feat: Add experience management feature with UI, BLoC integration, and repository implementation

This commit is contained in:
Achintha Isuru
2026-01-24 21:04:59 -05:00
parent c124111f46
commit f81e1949d1
23 changed files with 901 additions and 1 deletions

View File

@@ -524,6 +524,42 @@
"locations_hint": "Downtown, Midtown, Brooklyn...",
"save_button": "Save Changes",
"save_success": "Personal info saved successfully"
},
"experience": {
"title": "Experience & Skills",
"industries_title": "Industries",
"industries_subtitle": "Select the industries you have experience in",
"skills_title": "Skills",
"skills_subtitle": "Select your skills or add custom ones",
"custom_skills_title": "Custom Skills:",
"custom_skill_hint": "Add custom skill...",
"save_button": "Save & Continue",
"industries": {
"hospitality": "Hospitality",
"food_service": "Food Service",
"warehouse": "Warehouse",
"events": "Events",
"retail": "Retail",
"healthcare": "Healthcare",
"other": "Other"
},
"skills": {
"food_service": "Food Service",
"bartending": "Bartending",
"event_setup": "Event Setup",
"hospitality": "Hospitality",
"warehouse": "Warehouse",
"customer_service": "Customer Service",
"cleaning": "Cleaning",
"security": "Security",
"retail": "Retail",
"cooking": "Cooking",
"cashier": "Cashier",
"server": "Server",
"barista": "Barista",
"host_hostess": "Host/Hostess",
"busser": "Busser"
}
}
}
}

View File

@@ -3,6 +3,7 @@ import 'mocks/auth_repository_mock.dart';
import 'mocks/business_repository_mock.dart';
import 'mocks/home_repository_mock.dart';
import 'mocks/order_repository_mock.dart';
import 'mocks/profile_repository_mock.dart';
/// A module that provides Data Connect dependencies, including mocks.
class DataConnectModule extends Module {
@@ -10,6 +11,7 @@ class DataConnectModule extends Module {
void exportedBinds(Injector i) {
// Make these mocks available to any module that imports this one.
i.addLazySingleton(AuthRepositoryMock.new);
i.addLazySingleton(ProfileRepositoryMock.new);
i.addLazySingleton(HomeRepositoryMock.new);
i.addLazySingleton(BusinessRepositoryMock.new);
i.addLazySingleton(OrderRepositoryMock.new);

View File

@@ -65,4 +65,26 @@ class ProfileRepositoryMock {
await Future.delayed(const Duration(seconds: 1));
// Simulate save
}
/// Fetches selected industries for the given staff ID.
Future<List<String>> getStaffIndustries(String staffId) async {
await Future.delayed(const Duration(milliseconds: 500));
return ['hospitality', 'events'];
}
/// Fetches selected skills for the given staff ID.
Future<List<String>> getStaffSkills(String staffId) async {
await Future.delayed(const Duration(milliseconds: 500));
return ['Bartending', 'Server'];
}
/// Saves experience (industries and skills) for the given staff ID.
Future<void> saveExperience(
String staffId,
List<String> industries,
List<String> skills,
) async {
await Future.delayed(const Duration(seconds: 1));
// Simulate save
}
}

View File

@@ -18,7 +18,7 @@ extension ProfileNavigator on IModularNavigator {
/// Navigates to the experience page.
void pushExperience() {
pushNamed('/profile/onboarding/experience');
pushNamed('/profile/experience');
}
/// Navigates to the attire page.

View File

@@ -3,6 +3,7 @@ import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:staff_profile_info/staff_profile_info.dart';
import 'package:staff_emergency_contact/staff_emergency_contact.dart';
import 'package:staff_profile_experience/staff_profile_experience.dart';
import 'data/repositories/profile_repository_impl.dart';
import 'domain/repositories/profile_repository.dart';
@@ -55,5 +56,6 @@ class StaffProfileModule extends Module {
r.child('/', child: (BuildContext context) => const StaffProfilePage());
r.module('/onboarding', module: StaffProfileInfoModule());
r.module('/emergency-contact', module: StaffEmergencyContactModule());
r.module('/experience', module: StaffProfileExperienceModule());
}
}

View File

@@ -34,6 +34,8 @@ dependencies:
path: ../profile_sections/onboarding/profile_info
staff_emergency_contact:
path: ../profile_sections/onboarding/emergency_contact
staff_profile_experience:
path: ../profile_sections/onboarding/experience
dev_dependencies:
flutter_test:

View File

@@ -0,0 +1 @@
# include: package:flutter_lints/flutter.yaml

View File

@@ -0,0 +1,29 @@
import 'package:krow_data_connect/krow_data_connect.dart';
import '../../domain/repositories/experience_repository_interface.dart';
/// Implementation of [ExperienceRepositoryInterface] that delegates to Data Connect.
class ExperienceRepositoryImpl implements ExperienceRepositoryInterface {
final ProfileRepositoryMock _mockRepository;
/// Creates a [ExperienceRepositoryImpl] with the given [ProfileRepositoryMock].
ExperienceRepositoryImpl(this._mockRepository);
@override
Future<List<String>> getIndustries(String staffId) {
return _mockRepository.getStaffIndustries(staffId);
}
@override
Future<List<String>> getSkills(String staffId) {
return _mockRepository.getStaffSkills(staffId);
}
@override
Future<void> saveExperience(
String staffId,
List<String> industries,
List<String> skills,
) {
return _mockRepository.saveExperience(staffId, industries, skills);
}
}

View File

@@ -0,0 +1,10 @@
import 'package:krow_core/core.dart';
class GetExperienceArguments extends UseCaseArgument {
final String staffId;
GetExperienceArguments({required this.staffId});
@override
List<Object?> get props => [staffId];
}

View File

@@ -0,0 +1,16 @@
import 'package:krow_core/core.dart';
class SaveExperienceArguments extends UseCaseArgument {
final String staffId;
final List<String> industries;
final List<String> skills;
SaveExperienceArguments({
required this.staffId,
required this.industries,
required this.skills,
});
@override
List<Object?> get props => [staffId, industries, skills];
}

View File

@@ -0,0 +1,15 @@
/// Interface for accessing staff experience data.
abstract class ExperienceRepositoryInterface {
/// Fetches the list of industries associated with the staff member.
Future<List<String>> getIndustries(String staffId);
/// Fetches the list of skills associated with the staff member.
Future<List<String>> getSkills(String staffId);
/// Saves the staff member's experience (industries and skills).
Future<void> saveExperience(
String staffId,
List<String> industries,
List<String> skills,
);
}

View File

@@ -0,0 +1,15 @@
import 'package:krow_core/core.dart';
import '../arguments/get_experience_arguments.dart';
import '../repositories/experience_repository_interface.dart';
/// Use case for fetching staff industries.
class GetStaffIndustriesUseCase implements UseCase<GetExperienceArguments, List<String>> {
final ExperienceRepositoryInterface _repository;
GetStaffIndustriesUseCase(this._repository);
@override
Future<List<String>> call(GetExperienceArguments input) {
return _repository.getIndustries(input.staffId);
}
}

View File

@@ -0,0 +1,15 @@
import 'package:krow_core/core.dart';
import '../arguments/get_experience_arguments.dart';
import '../repositories/experience_repository_interface.dart';
/// Use case for fetching staff skills.
class GetStaffSkillsUseCase implements UseCase<GetExperienceArguments, List<String>> {
final ExperienceRepositoryInterface _repository;
GetStaffSkillsUseCase(this._repository);
@override
Future<List<String>> call(GetExperienceArguments input) {
return _repository.getSkills(input.staffId);
}
}

View File

@@ -0,0 +1,22 @@
import 'package:krow_core/core.dart';
import '../arguments/save_experience_arguments.dart';
import '../repositories/experience_repository_interface.dart';
/// Use case for saving staff experience details.
///
/// Delegates the saving logic to [ExperienceRepositoryInterface].
class SaveExperienceUseCase extends UseCase<SaveExperienceArguments, void> {
final ExperienceRepositoryInterface repository;
/// Creates a [SaveExperienceUseCase].
SaveExperienceUseCase(this.repository);
@override
Future<void> call(SaveExperienceArguments params) {
return repository.saveExperience(
params.staffId,
params.industries,
params.skills,
);
}
}

View File

@@ -0,0 +1,227 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../domain/arguments/get_experience_arguments.dart';
import '../../domain/arguments/save_experience_arguments.dart';
import '../../domain/usecases/get_staff_industries_usecase.dart';
import '../../domain/usecases/get_staff_skills_usecase.dart';
import '../../domain/usecases/save_experience_usecase.dart';
// Events
abstract class ExperienceEvent extends Equatable {
const ExperienceEvent();
@override
List<Object?> get props => [];
}
class ExperienceLoaded extends ExperienceEvent {}
class ExperienceIndustryToggled extends ExperienceEvent {
final String industry;
const ExperienceIndustryToggled(this.industry);
@override
List<Object?> get props => [industry];
}
class ExperienceSkillToggled extends ExperienceEvent {
final String skill;
const ExperienceSkillToggled(this.skill);
@override
List<Object?> get props => [skill];
}
class ExperienceCustomSkillAdded extends ExperienceEvent {
final String skill;
const ExperienceCustomSkillAdded(this.skill);
@override
List<Object?> get props => [skill];
}
class ExperienceSubmitted extends ExperienceEvent {}
// State
enum ExperienceStatus { initial, loading, success, failure }
class ExperienceState extends Equatable {
final ExperienceStatus status;
final List<String> selectedIndustries;
final List<String> selectedSkills;
final List<String> availableIndustries;
final List<String> availableSkills;
final String? errorMessage;
const ExperienceState({
this.status = ExperienceStatus.initial,
this.selectedIndustries = const [],
this.selectedSkills = const [],
this.availableIndustries = const [],
this.availableSkills = const [],
this.errorMessage,
});
ExperienceState copyWith({
ExperienceStatus? status,
List<String>? selectedIndustries,
List<String>? selectedSkills,
List<String>? availableIndustries,
List<String>? availableSkills,
String? errorMessage,
}) {
return ExperienceState(
status: status ?? this.status,
selectedIndustries: selectedIndustries ?? this.selectedIndustries,
selectedSkills: selectedSkills ?? this.selectedSkills,
availableIndustries: availableIndustries ?? this.availableIndustries,
availableSkills: availableSkills ?? this.availableSkills,
errorMessage: errorMessage ?? this.errorMessage,
);
}
@override
List<Object?> get props => [
status,
selectedIndustries,
selectedSkills,
availableIndustries,
availableSkills,
errorMessage,
];
}
// BLoC
class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState> {
static const List<String> _kAllIndustries = [
'hospitality',
'food_service',
'warehouse',
'events',
'retail',
'healthcare',
'other',
];
static const List<String> _kAllSkills = [
'food_service',
'bartending',
'event_setup',
'hospitality',
'warehouse',
'customer_service',
'cleaning',
'security',
'retail',
'cooking',
'cashier',
'server',
'barista',
'host_hostess',
'busser',
];
final GetStaffIndustriesUseCase getIndustries;
final GetStaffSkillsUseCase getSkills;
final SaveExperienceUseCase saveExperience;
final String staffId;
ExperienceBloc({
required this.getIndustries,
required this.getSkills,
required this.saveExperience,
required this.staffId,
}) : super(const ExperienceState(
availableIndustries: _kAllIndustries,
availableSkills: _kAllSkills,
)) {
on<ExperienceLoaded>(_onLoaded);
on<ExperienceIndustryToggled>(_onIndustryToggled);
on<ExperienceSkillToggled>(_onSkillToggled);
on<ExperienceCustomSkillAdded>(_onCustomSkillAdded);
on<ExperienceSubmitted>(_onSubmitted);
}
Future<void> _onLoaded(
ExperienceLoaded event,
Emitter<ExperienceState> emit,
) async {
emit(state.copyWith(status: ExperienceStatus.loading));
try {
final arguments = GetExperienceArguments(staffId: staffId);
final results = await Future.wait([
getIndustries(arguments),
getSkills(arguments),
]);
emit(state.copyWith(
status: ExperienceStatus.initial,
selectedIndustries: results[0],
selectedSkills: results[1],
));
} catch (e) {
emit(state.copyWith(
status: ExperienceStatus.failure,
errorMessage: e.toString(),
));
}
}
void _onIndustryToggled(
ExperienceIndustryToggled event,
Emitter<ExperienceState> emit,
) {
final industries = List<String>.from(state.selectedIndustries);
if (industries.contains(event.industry)) {
industries.remove(event.industry);
} else {
industries.add(event.industry);
}
emit(state.copyWith(selectedIndustries: industries));
}
void _onSkillToggled(
ExperienceSkillToggled event,
Emitter<ExperienceState> emit,
) {
final skills = List<String>.from(state.selectedSkills);
if (skills.contains(event.skill)) {
skills.remove(event.skill);
} else {
skills.add(event.skill);
}
emit(state.copyWith(selectedSkills: skills));
}
void _onCustomSkillAdded(
ExperienceCustomSkillAdded event,
Emitter<ExperienceState> emit,
) {
if (!state.selectedSkills.contains(event.skill)) {
final skills = List<String>.from(state.selectedSkills)..add(event.skill);
emit(state.copyWith(selectedSkills: skills));
}
}
Future<void> _onSubmitted(
ExperienceSubmitted event,
Emitter<ExperienceState> emit,
) async {
emit(state.copyWith(status: ExperienceStatus.loading));
try {
await saveExperience(
SaveExperienceArguments(
staffId: staffId,
industries: state.selectedIndustries,
skills: state.selectedSkills,
),
);
emit(state.copyWith(status: ExperienceStatus.success));
} catch (e) {
emit(state.copyWith(
status: ExperienceStatus.failure,
errorMessage: e.toString(),
));
}
}
}

View File

@@ -0,0 +1,6 @@
import 'package:flutter_modular/flutter_modular.dart';
extension ExperienceNavigator on IModularNavigator {
// Add navigation methods here if the page navigates deeper.
// Currently ExperiencePage is a leaf, but might need to navigate back or to success screen.
}

View File

@@ -0,0 +1,240 @@
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';
import '../blocs/experience_bloc.dart';
import '../widgets/experience_badge.dart';
import '../widgets/experience_custom_input.dart';
import '../widgets/experience_section_title.dart';
class ExperiencePage extends StatelessWidget {
const ExperiencePage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => Modular.get<ExperienceBloc>()..add(ExperienceLoaded()),
child: const _ExperienceView(),
);
}
}
class _ExperienceView extends StatelessWidget {
const _ExperienceView();
String _getIndustryLabel(dynamic node, String key) {
switch (key) {
case 'hospitality': return node.hospitality;
case 'food_service': return node.food_service;
case 'warehouse': return node.warehouse;
case 'events': return node.events;
case 'retail': return node.retail;
case 'healthcare': return node.healthcare;
case 'other': return node.other;
default: return key;
}
}
String _getSkillLabel(dynamic node, String key) {
switch (key) {
case 'food_service': return node.food_service;
case 'bartending': return node.bartending;
case 'event_setup': return node.event_setup;
case 'hospitality': return node.hospitality;
case 'warehouse': return node.warehouse;
case 'customer_service': return node.customer_service;
case 'cleaning': return node.cleaning;
case 'security': return node.security;
case 'retail': return node.retail;
case 'cooking': return node.cooking;
case 'cashier': return node.cashier;
case 'server': return node.server;
case 'barista': return node.barista;
case 'host_hostess': return node.host_hostess;
case 'busser': return node.busser;
default: return key;
}
}
@override
Widget build(BuildContext context) {
final i18n = t.staff.onboarding.experience;
return Scaffold(
backgroundColor: UiColors.background,
appBar: AppBar(
backgroundColor: UiColors.bgPopup,
elevation: 0,
leading: IconButton(
icon: Icon(UiIcons.chevronLeft, color: UiColors.textSecondary),
onPressed: () => Modular.to.pop(),
),
title: Text(
i18n.title,
style: UiTypography.title1m.copyWith(color: UiColors.textPrimary),
),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0),
child: Container(color: UiColors.border, height: 1.0),
),
),
body: BlocConsumer<ExperienceBloc, ExperienceState>(
listener: (context, state) {
if (state.status == ExperienceStatus.success) {
Modular.to.pop();
}
},
builder: (context, state) {
return Column(
children: [
Expanded(
child: SingleChildScrollView(
padding: EdgeInsets.all(UiConstants.space5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ExperienceSectionTitle(title: i18n.industries_title),
Text(
i18n.industries_subtitle,
style: UiTypography.body2m.copyWith(color: UiColors.textSecondary),
),
SizedBox(height: UiConstants.space3),
Wrap(
spacing: UiConstants.space2,
runSpacing: UiConstants.space2,
children: state.availableIndustries
.map(
(i) => ExperienceBadge(
label: _getIndustryLabel(i18n.industries, i),
isSelected: state.selectedIndustries.contains(i),
onTap: () => BlocProvider.of<ExperienceBloc>(context)
.add(ExperienceIndustryToggled(i)),
isIndustry: true,
),
)
.toList(),
),
SizedBox(height: UiConstants.space6),
ExperienceSectionTitle(title: i18n.skills_title),
Text(
i18n.skills_subtitle,
style: UiTypography.body2m.copyWith(color: UiColors.textSecondary),
),
SizedBox(height: UiConstants.space3),
Wrap(
spacing: UiConstants.space2,
runSpacing: UiConstants.space2,
children: state.availableSkills
.map(
(s) => ExperienceBadge(
label: _getSkillLabel(i18n.skills, s),
isSelected: state.selectedSkills.contains(s),
onTap: () => BlocProvider.of<ExperienceBloc>(context)
.add(ExperienceSkillToggled(s)),
),
)
.toList(),
),
SizedBox(height: UiConstants.space4),
const ExperienceCustomInput(),
SizedBox(height: UiConstants.space4),
_buildCustomSkillsList(state, i18n),
SizedBox(height: UiConstants.space10),
],
),
),
),
_buildSaveButton(context, state, i18n),
],
);
},
),
);
}
Widget _buildCustomSkillsList(ExperienceState state, dynamic i18n) {
final customSkills = state.selectedSkills
.where((s) => !state.availableSkills.contains(s))
.toList();
if (customSkills.isEmpty) return const SizedBox.shrink();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
i18n.custom_skills_title,
style: UiTypography.body2m.copyWith(color: UiColors.textSecondary),
),
SizedBox(height: UiConstants.space2),
Wrap(
spacing: UiConstants.space2,
runSpacing: UiConstants.space2,
children: customSkills.map((skill) {
return Container(
padding: EdgeInsets.symmetric(
horizontal: UiConstants.space3,
vertical: UiConstants.space2,
),
decoration: BoxDecoration(
color: UiColors.accent,
borderRadius: UiConstants.radiusFull,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
skill,
style: UiTypography.body2m.copyWith(color: UiColors.textPrimary),
),
],
),
);
}).toList(),
),
],
);
}
Widget _buildSaveButton(BuildContext context, ExperienceState state, dynamic i18n) {
return Container(
padding: EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
color: UiColors.bgPopup,
border: Border(top: BorderSide(color: UiColors.border)),
),
child: SafeArea(
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: state.status == ExperienceStatus.loading
? null
: () => BlocProvider.of<ExperienceBloc>(context).add(ExperienceSubmitted()),
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary,
foregroundColor: UiColors.primaryForeground,
padding: EdgeInsets.symmetric(vertical: UiConstants.space4),
shape: RoundedRectangleBorder(
borderRadius: UiConstants.radiusFull,
),
elevation: 0,
),
child: state.status == ExperienceStatus.loading
? SizedBox(
height: 20.0,
width: 20.0,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(UiColors.primaryForeground),
),
)
: Text(
i18n.save_button,
style: UiTypography.title2b,
),
),
),
),
);
}
}

View File

@@ -0,0 +1,43 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
class ExperienceBadge extends StatelessWidget {
final String label;
final bool isSelected;
final VoidCallback onTap;
final bool isIndustry;
const ExperienceBadge({
super.key,
required this.label,
required this.isSelected,
required this.onTap,
this.isIndustry = false,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: EdgeInsets.symmetric(
horizontal: UiConstants.space3,
vertical: UiConstants.space2,
),
decoration: BoxDecoration(
color: isSelected ? UiColors.primary : Colors.transparent,
borderRadius: UiConstants.radiusFull,
border: Border.all(
color: isSelected ? UiColors.primary : UiColors.border,
),
),
child: Text(
label,
style: UiTypography.body2m.copyWith(
color: isSelected ? UiColors.primaryForeground : UiColors.textSecondary,
),
),
),
);
}
}

View File

@@ -0,0 +1,83 @@
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 '../blocs/experience_bloc.dart';
class ExperienceCustomInput extends StatefulWidget {
const ExperienceCustomInput({super.key});
@override
State<ExperienceCustomInput> createState() => _ExperienceCustomInputState();
}
class _ExperienceCustomInputState extends State<ExperienceCustomInput> {
final TextEditingController _controller = TextEditingController();
void _addSkill() {
final skill = _controller.text.trim();
if (skill.isNotEmpty) {
BlocProvider.of<ExperienceBloc>(context).add(ExperienceCustomSkillAdded(skill));
_controller.clear();
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: TextField(
controller: _controller,
onSubmitted: (_) => _addSkill(),
style: UiTypography.body1r.copyWith(color: UiColors.textPrimary),
decoration: InputDecoration(
hintText: t.staff.onboarding.experience.custom_skill_hint,
hintStyle: UiTypography.body1r.copyWith(color: UiColors.textPlaceholder),
contentPadding: EdgeInsets.symmetric(
horizontal: UiConstants.space3,
vertical: UiConstants.space3,
),
border: OutlineInputBorder(
borderRadius: UiConstants.radiusMd,
borderSide: BorderSide(color: UiColors.border),
),
enabledBorder: OutlineInputBorder(
borderRadius: UiConstants.radiusMd,
borderSide: BorderSide(color: UiColors.border),
),
focusedBorder: OutlineInputBorder(
borderRadius: UiConstants.radiusMd,
borderSide: BorderSide(color: UiColors.primary),
),
),
fillColor: UiColors.bgPopup,
filled: true,
),
),
),
SizedBox(width: UiConstants.space2),
InkWell(
onTap: _addSkill,
child: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: UiColors.primary,
borderRadius: BorderRadius.circular(UiConstants.radiusMd),
),
child: Center(
child: Icon(UiIcons.add, color: UiColors.primaryForeground),
),
),
),
],
);
}
}

View File

@@ -0,0 +1,20 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
class ExperienceSectionTitle extends StatelessWidget {
final String title;
const ExperienceSectionTitle({super.key, required this.title});
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(bottom: UiConstants.space2),
child: Text(
title,
style: UiTypography.title2m.copyWith(
color: UiColors.textPrimary,
),
),
);
}
}

View File

@@ -0,0 +1,58 @@
library staff_profile_experience;
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_data_connect/krow_data_connect.dart';
import 'src/data/repositories/experience_repository_impl.dart';
import 'src/domain/repositories/experience_repository_interface.dart';
import 'src/domain/usecases/get_staff_industries_usecase.dart';
import 'src/domain/usecases/get_staff_skills_usecase.dart';
import 'src/domain/usecases/save_experience_usecase.dart';
import 'src/presentation/blocs/experience_bloc.dart';
import 'src/presentation/pages/experience_page.dart';
export 'src/presentation/pages/experience_page.dart';
class StaffProfileExperienceModule extends Module {
@override
List<Module> get imports => [DataConnectModule()];
@override
void binds(Injector i) {
// Repository
i.addLazySingleton<ExperienceRepositoryInterface>(
() => ExperienceRepositoryImpl(i.get<ProfileRepositoryMock>()),
);
// UseCases
i.addLazySingleton<GetStaffIndustriesUseCase>(
() => GetStaffIndustriesUseCase(i.get<ExperienceRepositoryInterface>()),
);
i.addLazySingleton<GetStaffSkillsUseCase>(
() => GetStaffSkillsUseCase(i.get<ExperienceRepositoryInterface>()),
);
i.addLazySingleton<SaveExperienceUseCase>(
() => SaveExperienceUseCase(i.get<ExperienceRepositoryInterface>()),
);
// BLoC
i.addLazySingleton<ExperienceBloc>(
() => ExperienceBloc(
getIndustries: i.get<GetStaffIndustriesUseCase>(),
getSkills: i.get<GetStaffSkillsUseCase>(),
saveExperience: i.get<SaveExperienceUseCase>(),
// TODO: Get actual logged in staff ID
staffId: 'current-staff-id',
),
);
}
@override
void routes(RouteManager r) {
r.child(
'/',
child: (_) => const ExperiencePage(),
transition: TransitionType.rightToLeft,
);
}
}

View File

@@ -0,0 +1,35 @@
name: staff_profile_experience
description: Staff Profile Experience feature.
version: 0.0.1
publish_to: none
resolution: workspace
environment:
sdk: '>=3.10.0 <4.0.0'
flutter: ">=3.0.0"
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.1.0
flutter_modular: ^6.3.0
equatable: ^2.0.5
# Architecture Packages
krow_domain:
path: ../../../../../../domain
krow_core:
path: ../../../../../../core
krow_data_connect:
path: ../../../../../../data_connect
design_system:
path: ../../../../../../design_system
core_localization:
path: ../../../../../../core_localization
dev_dependencies:
flutter_test:
sdk: flutter
bloc_test: ^9.1.0
mocktail: ^1.0.0
flutter_lints: ^6.0.0

View File

@@ -14,6 +14,7 @@ workspace:
- packages/features/staff/staff_main
- packages/features/staff/profile
- packages/features/staff/profile_sections/onboarding/emergency_contact
- packages/features/staff/profile_sections/onboarding/experience
- packages/features/staff/profile_sections/onboarding/profile_info
- packages/features/client/authentication
- packages/features/client/home