feat: Implement Google Places Autocomplete for Staff Location
- Implemented strictly filtered Google Places Autocomplete (cities only) for Staff Profile Setup. - Centralized Google Places API Key configuration in Core AppConfig. - Updated Client Hubs to use the centralized AppConfig. - Verified ViewOrdersCubit logic for weekly order summaries.
This commit is contained in:
@@ -4,3 +4,4 @@ export 'src/domain/arguments/usecase_argument.dart';
|
||||
export 'src/domain/usecases/usecase.dart';
|
||||
export 'src/utils/date_time_utils.dart';
|
||||
export 'src/presentation/widgets/web_mobile_frame.dart';
|
||||
export 'src/config/app_config.dart';
|
||||
|
||||
5
apps/mobile/packages/core/lib/src/config/app_config.dart
Normal file
5
apps/mobile/packages/core/lib/src/config/app_config.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
class AppConfig {
|
||||
AppConfig._();
|
||||
|
||||
static const String googlePlacesApiKey = String.fromEnvironment('GOOGLE_PLACES_API_KEY');
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:krow_core/krow_core.dart';
|
||||
|
||||
class HubsConstants {
|
||||
static const String googlePlacesApiKey = String.fromEnvironment('GOOGLE_PLACES_API_KEY');
|
||||
static const String googlePlacesApiKey = AppConfig.googlePlacesApiKey;
|
||||
static const List<String> supportedCountries = <String>['us'];
|
||||
}
|
||||
|
||||
@@ -255,20 +255,12 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
|
||||
}
|
||||
|
||||
int _calculateCategoryCount(String category) {
|
||||
if (state.selectedDate == null) return 0;
|
||||
final String selectedDateStr = DateFormat(
|
||||
'yyyy-MM-dd',
|
||||
).format(state.selectedDate!);
|
||||
final List<OrderItem> ordersOnDate = state.orders
|
||||
.where((OrderItem s) => s.date == selectedDateStr)
|
||||
.toList();
|
||||
|
||||
if (category == 'active') {
|
||||
return ordersOnDate
|
||||
return state.orders
|
||||
.where((OrderItem s) => s.status == 'IN_PROGRESS')
|
||||
.length;
|
||||
} else if (category == 'completed') {
|
||||
return ordersOnDate
|
||||
return state.orders
|
||||
.where((OrderItem s) => s.status == 'COMPLETED')
|
||||
.length;
|
||||
}
|
||||
@@ -276,14 +268,7 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
|
||||
}
|
||||
|
||||
int _calculateUpNextCount() {
|
||||
if (state.selectedDate == null) return 0;
|
||||
final String selectedDateStr = DateFormat(
|
||||
'yyyy-MM-dd',
|
||||
).format(state.selectedDate!);
|
||||
final List<OrderItem> ordersOnDate = state.orders
|
||||
.where((OrderItem s) => s.date == selectedDateStr)
|
||||
.toList();
|
||||
return ordersOnDate
|
||||
return state.orders
|
||||
.where(
|
||||
(OrderItem s) =>
|
||||
// TODO(orders): move PENDING to its own tab once available.
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:view_orders/src/presentation/blocs/view_orders_cubit.dart';
|
||||
import 'package:view_orders/src/presentation/blocs/view_orders_state.dart';
|
||||
import 'package:view_orders/src/domain/usecases/get_orders_use_case.dart';
|
||||
import 'package:view_orders/src/domain/usecases/get_accepted_applications_for_day_use_case.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:view_orders/src/domain/arguments/orders_range_arguments.dart';
|
||||
import 'package:view_orders/src/domain/arguments/orders_day_arguments.dart';
|
||||
|
||||
class MockGetOrdersUseCase extends Mock implements GetOrdersUseCase {}
|
||||
class MockGetAcceptedAppsUseCase extends Mock implements GetAcceptedApplicationsForDayUseCase {}
|
||||
|
||||
void main() {
|
||||
group('ViewOrdersCubit', () {
|
||||
late GetOrdersUseCase getOrdersUseCase;
|
||||
late GetAcceptedApplicationsForDayUseCase getAcceptedAppsUseCase;
|
||||
|
||||
setUp(() {
|
||||
getOrdersUseCase = MockGetOrdersUseCase();
|
||||
getAcceptedAppsUseCase = MockGetAcceptedAppsUseCase();
|
||||
registerFallbackValue(OrdersRangeArguments(start: DateTime.now(), end: DateTime.now()));
|
||||
registerFallbackValue(OrdersDayArguments(day: DateTime.now()));
|
||||
});
|
||||
|
||||
test('initial state is correct', () {
|
||||
final cubit = ViewOrdersCubit(
|
||||
getOrdersUseCase: getOrdersUseCase,
|
||||
getAcceptedAppsUseCase: getAcceptedAppsUseCase,
|
||||
);
|
||||
expect(cubit.state.status, ViewOrdersStatus.initial);
|
||||
cubit.close();
|
||||
});
|
||||
|
||||
blocTest<ViewOrdersCubit, ViewOrdersState>(
|
||||
'calculates upNextCount based on ALL loaded orders, not just the selected day',
|
||||
build: () {
|
||||
final mockOrders = [
|
||||
// Order 1: Today (Matches selected date)
|
||||
OrderItem(
|
||||
id: '1', orderId: '1', title: 'Order 1', clientName: 'Client',
|
||||
status: 'OPEN', date: '2026-02-04', startTime: '09:00', endTime: '17:00',
|
||||
location: 'Loc', locationAddress: 'Addr', filled: 0, workersNeeded: 1,
|
||||
hourlyRate: 20, hours: 8, totalValue: 160
|
||||
),
|
||||
// Order 2: Tomorrow (Different date)
|
||||
OrderItem(
|
||||
id: '2', orderId: '2', title: 'Order 2', clientName: 'Client',
|
||||
status: 'OPEN', date: '2026-02-05', startTime: '09:00', endTime: '17:00',
|
||||
location: 'Loc', locationAddress: 'Addr', filled: 0, workersNeeded: 1,
|
||||
hourlyRate: 20, hours: 8, totalValue: 160
|
||||
),
|
||||
];
|
||||
|
||||
when(() => getOrdersUseCase(any())).thenAnswer((_) async => mockOrders);
|
||||
when(() => getAcceptedAppsUseCase(any())).thenAnswer((_) async => {});
|
||||
|
||||
return ViewOrdersCubit(
|
||||
getOrdersUseCase: getOrdersUseCase,
|
||||
getAcceptedAppsUseCase: getAcceptedAppsUseCase,
|
||||
);
|
||||
},
|
||||
act: (cubit) async {
|
||||
// Wait for init to trigger load
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
// Select 'Today' (2026-02-04 matches Order 1)
|
||||
cubit.selectDate(DateTime(2026, 02, 04));
|
||||
},
|
||||
verify: (cubit) {
|
||||
// Assert:
|
||||
// 1. filteredOrders should only have 1 order (the one for the selected date)
|
||||
expect(cubit.state.filteredOrders.length, 1, reason: 'Should only show orders for selected filtered date');
|
||||
expect(cubit.state.filteredOrders.first.id, '1');
|
||||
|
||||
// 2. upNextCount should have 2 orders (Total for the loaded week)
|
||||
expect(cubit.state.upNextCount, 2, reason: 'Up Next count should include ALL orders in the week range');
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
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();
|
||||
|
||||
@override
|
||||
Future<List<String>> searchCities(String query) async {
|
||||
if (query.isEmpty) return [];
|
||||
|
||||
final Uri uri = Uri.https(
|
||||
'maps.googleapis.com',
|
||||
'/maps/api/place/autocomplete/json',
|
||||
<String, String>{
|
||||
'input': query,
|
||||
'types': '(cities)',
|
||||
'key': AppConfig.googlePlacesApiKey,
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
final http.Response response = await _client.get(uri);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final Map<String, dynamic> data = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (data['status'] == 'OK' || data['status'] == 'ZERO_RESULTS') {
|
||||
final List<dynamic> predictions = data['predictions'] as List<dynamic>;
|
||||
|
||||
return predictions.map((dynamic prediction) {
|
||||
return prediction['description'] as String;
|
||||
}).toList();
|
||||
} else {
|
||||
// Handle other statuses (OVER_QUERY_LIMIT, REQUEST_DENIED, etc.)
|
||||
// Returning empty list for now to avoid crashing UI, ideally log this.
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
throw Exception('Network Error: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
abstract class PlaceRepository {
|
||||
/// Searches for cities matching the [query].
|
||||
/// Returns a list of city names.
|
||||
Future<List<String>> searchCities(String query);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import '../repositories/place_repository.dart';
|
||||
|
||||
class SearchCitiesUseCase {
|
||||
final PlaceRepository _repository;
|
||||
|
||||
SearchCitiesUseCase(this._repository);
|
||||
|
||||
Future<List<String>> call(String query) {
|
||||
return _repository.searchCities(query);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../domain/usecases/submit_profile_setup_usecase.dart';
|
||||
|
||||
import '../../../domain/usecases/search_cities_usecase.dart';
|
||||
|
||||
import 'profile_setup_event.dart';
|
||||
import 'profile_setup_state.dart';
|
||||
|
||||
@@ -11,7 +13,9 @@ export 'profile_setup_state.dart';
|
||||
class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState> {
|
||||
ProfileSetupBloc({
|
||||
required SubmitProfileSetup submitProfileSetup,
|
||||
required SearchCitiesUseCase searchCities,
|
||||
}) : _submitProfileSetup = submitProfileSetup,
|
||||
_searchCities = searchCities,
|
||||
super(const ProfileSetupState()) {
|
||||
on<ProfileSetupFullNameChanged>(_onFullNameChanged);
|
||||
on<ProfileSetupBioChanged>(_onBioChanged);
|
||||
@@ -20,9 +24,12 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState> {
|
||||
on<ProfileSetupSkillsChanged>(_onSkillsChanged);
|
||||
on<ProfileSetupIndustriesChanged>(_onIndustriesChanged);
|
||||
on<ProfileSetupSubmitted>(_onSubmitted);
|
||||
on<ProfileSetupLocationQueryChanged>(_onLocationQueryChanged);
|
||||
on<ProfileSetupClearLocationSuggestions>(_onClearLocationSuggestions);
|
||||
}
|
||||
|
||||
final SubmitProfileSetup _submitProfileSetup;
|
||||
final SearchCitiesUseCase _searchCities;
|
||||
|
||||
/// Handles the [ProfileSetupFullNameChanged] event.
|
||||
void _onFullNameChanged(
|
||||
@@ -99,4 +106,29 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onLocationQueryChanged(
|
||||
ProfileSetupLocationQueryChanged event,
|
||||
Emitter<ProfileSetupState> emit,
|
||||
) async {
|
||||
if (event.query.isEmpty) {
|
||||
emit(state.copyWith(locationSuggestions: []));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final results = await _searchCities(event.query);
|
||||
emit(state.copyWith(locationSuggestions: results));
|
||||
} catch (e) {
|
||||
// Quietly fail or clear
|
||||
emit(state.copyWith(locationSuggestions: []));
|
||||
}
|
||||
}
|
||||
|
||||
void _onClearLocationSuggestions(
|
||||
ProfileSetupClearLocationSuggestions event,
|
||||
Emitter<ProfileSetupState> emit,
|
||||
) {
|
||||
emit(state.copyWith(locationSuggestions: []));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,24 @@ class ProfileSetupIndustriesChanged extends ProfileSetupEvent {
|
||||
List<Object?> get props => <Object?>[industries];
|
||||
}
|
||||
|
||||
/// 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);
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[query];
|
||||
}
|
||||
|
||||
/// Event triggered when the location suggestions should be cleared.
|
||||
class ProfileSetupClearLocationSuggestions extends ProfileSetupEvent {
|
||||
/// Creates a [ProfileSetupClearLocationSuggestions] event.
|
||||
const ProfileSetupClearLocationSuggestions();
|
||||
}
|
||||
|
||||
/// Event triggered when the profile submission is requested.
|
||||
class ProfileSetupSubmitted extends ProfileSetupEvent {
|
||||
/// Creates a [ProfileSetupSubmitted] event.
|
||||
|
||||
@@ -26,8 +26,8 @@ class ProfileSetupState extends Equatable {
|
||||
/// The current status of the profile setup process.
|
||||
final ProfileSetupStatus status;
|
||||
|
||||
/// Error message if the status is [ProfileSetupStatus.failure].
|
||||
final String? errorMessage;
|
||||
/// List of location suggestions from the API.
|
||||
final List<String> locationSuggestions;
|
||||
|
||||
/// Creates a [ProfileSetupState] instance.
|
||||
const ProfileSetupState({
|
||||
@@ -39,6 +39,7 @@ class ProfileSetupState extends Equatable {
|
||||
this.industries = const <String>[],
|
||||
this.status = ProfileSetupStatus.initial,
|
||||
this.errorMessage,
|
||||
this.locationSuggestions = const <String>[],
|
||||
});
|
||||
|
||||
/// Creates a copy of the current state with updated values.
|
||||
@@ -51,6 +52,7 @@ class ProfileSetupState extends Equatable {
|
||||
List<String>? industries,
|
||||
ProfileSetupStatus? status,
|
||||
String? errorMessage,
|
||||
List<String>? locationSuggestions,
|
||||
}) {
|
||||
return ProfileSetupState(
|
||||
fullName: fullName ?? this.fullName,
|
||||
@@ -61,18 +63,20 @@ class ProfileSetupState extends Equatable {
|
||||
industries: industries ?? this.industries,
|
||||
status: status ?? this.status,
|
||||
errorMessage: errorMessage,
|
||||
locationSuggestions: locationSuggestions ?? this.locationSuggestions,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[
|
||||
fullName,
|
||||
bio,
|
||||
preferredLocations,
|
||||
maxDistanceMiles,
|
||||
skills,
|
||||
industries,
|
||||
status,
|
||||
errorMessage,
|
||||
];
|
||||
fullName,
|
||||
bio,
|
||||
preferredLocations,
|
||||
maxDistanceMiles,
|
||||
skills,
|
||||
industries,
|
||||
status,
|
||||
errorMessage,
|
||||
locationSuggestions,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:staff_authentication/src/presentation/widgets/common/section_title_subtitle.dart';
|
||||
import 'package:staff_authentication/staff_authentication.dart';
|
||||
|
||||
@@ -32,26 +34,38 @@ class ProfileSetupLocation extends StatefulWidget {
|
||||
|
||||
class _ProfileSetupLocationState extends State<ProfileSetupLocation> {
|
||||
final TextEditingController _locationController = TextEditingController();
|
||||
Timer? _debounce;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_locationController.dispose();
|
||||
_debounce?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Adds the current text from the controller as a location.
|
||||
void _addLocation() {
|
||||
final String loc = _locationController.text.trim();
|
||||
if (loc.isNotEmpty && !widget.preferredLocations.contains(loc)) {
|
||||
final List<String> updatedList = List<String>.from(widget.preferredLocations)
|
||||
..add(loc);
|
||||
void _onSearchChanged(String query) {
|
||||
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
||||
_debounce = Timer(const Duration(milliseconds: 300), () {
|
||||
context
|
||||
.read<ProfileSetupBloc>()
|
||||
.add(ProfileSetupLocationQueryChanged(query));
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds the selected location.
|
||||
void _addLocation(String location) {
|
||||
if (location.isNotEmpty && !widget.preferredLocations.contains(location)) {
|
||||
final List<String> updatedList =
|
||||
List<String>.from(widget.preferredLocations)..add(location);
|
||||
widget.onLocationsChanged(updatedList);
|
||||
_locationController.clear();
|
||||
context
|
||||
.read<ProfileSetupBloc>()
|
||||
.add(const ProfileSetupClearLocationSuggestions());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
/// Builds the location setup step UI.
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -62,37 +76,55 @@ class _ProfileSetupLocationState extends State<ProfileSetupLocation> {
|
||||
),
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
|
||||
// Add Location input
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
spacing: UiConstants.space2,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: UiTextField(
|
||||
label: t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.location
|
||||
.add_location_label,
|
||||
controller: _locationController,
|
||||
hintText: t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.location
|
||||
.add_location_hint,
|
||||
onSubmitted: (_) => _addLocation(),
|
||||
// Search Input
|
||||
UiTextField(
|
||||
label: t.staff_authentication.profile_setup_page.location
|
||||
.add_location_label,
|
||||
controller: _locationController,
|
||||
hintText: t.staff_authentication.profile_setup_page.location
|
||||
.add_location_hint,
|
||||
onChanged: _onSearchChanged,
|
||||
),
|
||||
|
||||
// Suggestions List
|
||||
BlocBuilder<ProfileSetupBloc, ProfileSetupState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.locationSuggestions != current.locationSuggestions,
|
||||
builder: (context, state) {
|
||||
if (state.locationSuggestions.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Container(
|
||||
constraints: const BoxConstraints(maxHeight: 200),
|
||||
margin: const EdgeInsets.only(top: UiConstants.space2),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusMd),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
UiButton.secondary(
|
||||
text:
|
||||
t.staff_authentication.profile_setup_page.location.add_button,
|
||||
onPressed: _addLocation,
|
||||
style: OutlinedButton.styleFrom(
|
||||
minimumSize: const Size(0, 48),
|
||||
maximumSize: const Size(double.infinity, 48),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: state.locationSuggestions.length,
|
||||
separatorBuilder: (context, index) => const Divider(height: 1),
|
||||
itemBuilder: (context, index) {
|
||||
final suggestion = state.locationSuggestions[index];
|
||||
return ListTile(
|
||||
title: Text(suggestion, style: UiTypography.body2m),
|
||||
leading: const Icon(UiIcons.mapPin, size: 16),
|
||||
onTap: () => _addLocation(suggestion),
|
||||
visualDensity: VisualDensity.compact,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
@@ -134,18 +166,12 @@ class _ProfileSetupLocationState extends State<ProfileSetupLocation> {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.location
|
||||
t.staff_authentication.profile_setup_page.location
|
||||
.min_dist_label,
|
||||
style: UiTypography.footnote1r.textSecondary,
|
||||
),
|
||||
Text(
|
||||
t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.location
|
||||
t.staff_authentication.profile_setup_page.location
|
||||
.max_dist_label,
|
||||
style: UiTypography.footnote1r.textSecondary,
|
||||
),
|
||||
@@ -158,8 +184,8 @@ class _ProfileSetupLocationState extends State<ProfileSetupLocation> {
|
||||
|
||||
/// Removes the specified [location] from the list.
|
||||
void _removeLocation({required String location}) {
|
||||
final List<String> updatedList = List<String>.from(widget.preferredLocations)
|
||||
..remove(location);
|
||||
final List<String> updatedList =
|
||||
List<String>.from(widget.preferredLocations)..remove(location);
|
||||
widget.onLocationsChanged(updatedList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ import 'package:staff_authentication/src/domain/usecases/verify_otp_usecase.dart
|
||||
import 'package:staff_authentication/src/domain/repositories/profile_setup_repository.dart';
|
||||
import 'package:staff_authentication/src/data/repositories_impl/profile_setup_repository_impl.dart';
|
||||
import 'package:staff_authentication/src/domain/usecases/submit_profile_setup_usecase.dart';
|
||||
import 'package:staff_authentication/src/domain/repositories/place_repository.dart';
|
||||
import 'package:staff_authentication/src/data/repositories_impl/place_repository_impl.dart';
|
||||
import 'package:staff_authentication/src/domain/usecases/search_cities_usecase.dart';
|
||||
import 'package:staff_authentication/src/presentation/blocs/auth_bloc.dart';
|
||||
import 'package:staff_authentication/src/presentation/blocs/profile_setup/profile_setup_bloc.dart';
|
||||
import 'package:staff_authentication/src/presentation/pages/get_started_page.dart';
|
||||
@@ -44,11 +47,13 @@ class StaffAuthenticationModule extends Module {
|
||||
dataConnect: ExampleConnector.instance,
|
||||
),
|
||||
);
|
||||
i.addLazySingleton<PlaceRepository>(PlaceRepositoryImpl.new);
|
||||
|
||||
// UseCases
|
||||
i.addLazySingleton(SignInWithPhoneUseCase.new);
|
||||
i.addLazySingleton(VerifyOtpUseCase.new);
|
||||
i.addLazySingleton(SubmitProfileSetup.new);
|
||||
i.addLazySingleton(SearchCitiesUseCase.new);
|
||||
|
||||
// BLoCs
|
||||
i.addLazySingleton<AuthBloc>(
|
||||
@@ -60,6 +65,7 @@ class StaffAuthenticationModule extends Module {
|
||||
i.add<ProfileSetupBloc>(
|
||||
() => ProfileSetupBloc(
|
||||
submitProfileSetup: i.get<SubmitProfileSetup>(),
|
||||
searchCities: i.get<SearchCitiesUseCase>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ dependencies:
|
||||
firebase_core: ^4.2.1
|
||||
firebase_auth: ^6.1.2 # Updated for compatibility
|
||||
firebase_data_connect: ^0.2.2+1
|
||||
http: ^1.2.0
|
||||
|
||||
# Architecture Packages
|
||||
krow_domain:
|
||||
|
||||
Reference in New Issue
Block a user