hubs ready
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:firebase_auth/firebase_auth.dart' as firebase;
|
||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart' as domain;
|
||||
|
||||
import '../../domain/repositories/hub_repository_interface.dart';
|
||||
import '../../util/hubs_constants.dart';
|
||||
|
||||
/// Implementation of [HubRepositoryInterface] backed by Data Connect.
|
||||
class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
@@ -27,10 +31,24 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
Future<domain.Hub> createHub({
|
||||
required String name,
|
||||
required String address,
|
||||
String? placeId,
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
String? city,
|
||||
String? state,
|
||||
String? street,
|
||||
String? country,
|
||||
String? zipCode,
|
||||
}) async {
|
||||
final dc.GetBusinessesByUserIdBusinesses business = await _getBusinessForCurrentUser();
|
||||
final String teamId = await _getOrCreateTeamId(business);
|
||||
final String? city = business.city;
|
||||
final _PlaceAddress? placeAddress =
|
||||
placeId == null || placeId.isEmpty ? null : await _fetchPlaceAddress(placeId);
|
||||
final String? cityValue = city ?? placeAddress?.city ?? business.city;
|
||||
final String? stateValue = state ?? placeAddress?.state;
|
||||
final String? streetValue = street ?? placeAddress?.street;
|
||||
final String? countryValue = country ?? placeAddress?.country;
|
||||
final String? zipCodeValue = zipCode ?? placeAddress?.zipCode;
|
||||
|
||||
final OperationResult<dc.CreateTeamHubData, dc.CreateTeamHubVariables> result = await _dataConnect
|
||||
.createTeamHub(
|
||||
@@ -38,7 +56,14 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
hubName: name,
|
||||
address: address,
|
||||
)
|
||||
.city(city?.isNotEmpty == true ? city : '')
|
||||
.placeId(placeId)
|
||||
.latitude(latitude)
|
||||
.longitude(longitude)
|
||||
.city(cityValue?.isNotEmpty == true ? cityValue : '')
|
||||
.state(stateValue)
|
||||
.street(streetValue)
|
||||
.country(countryValue)
|
||||
.zipCode(zipCodeValue)
|
||||
.execute();
|
||||
final String? createdId = result.data?.teamHub_insert.id;
|
||||
if (createdId == null) {
|
||||
@@ -192,4 +217,99 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<_PlaceAddress?> _fetchPlaceAddress(String placeId) async {
|
||||
final Uri uri = Uri.https(
|
||||
'maps.googleapis.com',
|
||||
'/maps/api/place/details/json',
|
||||
<String, String>{
|
||||
'place_id': placeId,
|
||||
'fields': 'address_component',
|
||||
'key': HubsConstants.googlePlacesApiKey,
|
||||
},
|
||||
);
|
||||
try {
|
||||
final http.Response response = await http.get(uri);
|
||||
if (response.statusCode != 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Map<String, dynamic> payload =
|
||||
json.decode(response.body) as Map<String, dynamic>;
|
||||
if (payload['status'] != 'OK') {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Map<String, dynamic>? result =
|
||||
payload['result'] as Map<String, dynamic>?;
|
||||
final List<dynamic>? components =
|
||||
result?['address_components'] as List<dynamic>?;
|
||||
if (components == null || components.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String? streetNumber;
|
||||
String? route;
|
||||
String? city;
|
||||
String? state;
|
||||
String? country;
|
||||
String? zipCode;
|
||||
|
||||
for (final dynamic entry in components) {
|
||||
final Map<String, dynamic> component = entry as Map<String, dynamic>;
|
||||
final List<dynamic> types = component['types'] as List<dynamic>? ?? <dynamic>[];
|
||||
final String? longName = component['long_name'] as String?;
|
||||
final String? shortName = component['short_name'] as String?;
|
||||
|
||||
if (types.contains('street_number')) {
|
||||
streetNumber = longName;
|
||||
} else if (types.contains('route')) {
|
||||
route = longName;
|
||||
} else if (types.contains('locality')) {
|
||||
city = longName;
|
||||
} else if (types.contains('postal_town')) {
|
||||
city ??= longName;
|
||||
} else if (types.contains('administrative_area_level_2')) {
|
||||
city ??= longName;
|
||||
} else if (types.contains('administrative_area_level_1')) {
|
||||
state = shortName ?? longName;
|
||||
} else if (types.contains('country')) {
|
||||
country = shortName ?? longName;
|
||||
} else if (types.contains('postal_code')) {
|
||||
zipCode = longName;
|
||||
}
|
||||
}
|
||||
|
||||
final String? streetValue = <String?>[streetNumber, route]
|
||||
.where((String? value) => value != null && value!.isNotEmpty)
|
||||
.join(' ')
|
||||
.trim();
|
||||
|
||||
return _PlaceAddress(
|
||||
street: streetValue?.isEmpty == true ? null : streetValue,
|
||||
city: city,
|
||||
state: state,
|
||||
country: country,
|
||||
zipCode: zipCode,
|
||||
);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _PlaceAddress {
|
||||
const _PlaceAddress({
|
||||
this.street,
|
||||
this.city,
|
||||
this.state,
|
||||
this.country,
|
||||
this.zipCode,
|
||||
});
|
||||
|
||||
final String? street;
|
||||
final String? city;
|
||||
final String? state;
|
||||
final String? country;
|
||||
final String? zipCode;
|
||||
}
|
||||
|
||||
@@ -10,11 +10,42 @@ class CreateHubArguments extends UseCaseArgument {
|
||||
/// The physical address of the hub.
|
||||
final String address;
|
||||
|
||||
final String? placeId;
|
||||
final double? latitude;
|
||||
final double? longitude;
|
||||
final String? city;
|
||||
final String? state;
|
||||
final String? street;
|
||||
final String? country;
|
||||
final String? zipCode;
|
||||
|
||||
/// Creates a [CreateHubArguments] instance.
|
||||
///
|
||||
/// Both [name] and [address] are required.
|
||||
const CreateHubArguments({required this.name, required this.address});
|
||||
const CreateHubArguments({
|
||||
required this.name,
|
||||
required this.address,
|
||||
this.placeId,
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
this.city,
|
||||
this.state,
|
||||
this.street,
|
||||
this.country,
|
||||
this.zipCode,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[name, address];
|
||||
List<Object?> get props => <Object?>[
|
||||
name,
|
||||
address,
|
||||
placeId,
|
||||
latitude,
|
||||
longitude,
|
||||
city,
|
||||
state,
|
||||
street,
|
||||
country,
|
||||
zipCode,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -15,7 +15,18 @@ abstract interface class HubRepositoryInterface {
|
||||
///
|
||||
/// Takes the [name] and [address] of the new hub.
|
||||
/// Returns the created [Hub] entity.
|
||||
Future<Hub> createHub({required String name, required String address});
|
||||
Future<Hub> createHub({
|
||||
required String name,
|
||||
required String address,
|
||||
String? placeId,
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
String? city,
|
||||
String? state,
|
||||
String? street,
|
||||
String? country,
|
||||
String? zipCode,
|
||||
});
|
||||
|
||||
/// Deletes a hub by its [id].
|
||||
Future<void> deleteHub(String id);
|
||||
|
||||
@@ -21,6 +21,14 @@ class CreateHubUseCase implements UseCase<CreateHubArguments, Hub> {
|
||||
return _repository.createHub(
|
||||
name: arguments.name,
|
||||
address: arguments.address,
|
||||
placeId: arguments.placeId,
|
||||
latitude: arguments.latitude,
|
||||
longitude: arguments.longitude,
|
||||
city: arguments.city,
|
||||
state: arguments.state,
|
||||
street: arguments.street,
|
||||
country: arguments.country,
|
||||
zipCode: arguments.zipCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,18 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
|
||||
try {
|
||||
await _createHubUseCase(
|
||||
CreateHubArguments(name: event.name, address: event.address),
|
||||
CreateHubArguments(
|
||||
name: event.name,
|
||||
address: event.address,
|
||||
placeId: event.placeId,
|
||||
latitude: event.latitude,
|
||||
longitude: event.longitude,
|
||||
city: event.city,
|
||||
state: event.state,
|
||||
street: event.street,
|
||||
country: event.country,
|
||||
zipCode: event.zipCode,
|
||||
),
|
||||
);
|
||||
final List<Hub> hubs = await _getHubsUseCase();
|
||||
emit(
|
||||
|
||||
@@ -18,11 +18,41 @@ class ClientHubsFetched extends ClientHubsEvent {
|
||||
class ClientHubsAddRequested extends ClientHubsEvent {
|
||||
final String name;
|
||||
final String address;
|
||||
final String? placeId;
|
||||
final double? latitude;
|
||||
final double? longitude;
|
||||
final String? city;
|
||||
final String? state;
|
||||
final String? street;
|
||||
final String? country;
|
||||
final String? zipCode;
|
||||
|
||||
const ClientHubsAddRequested({required this.name, required this.address});
|
||||
const ClientHubsAddRequested({
|
||||
required this.name,
|
||||
required this.address,
|
||||
this.placeId,
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
this.city,
|
||||
this.state,
|
||||
this.street,
|
||||
this.country,
|
||||
this.zipCode,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[name, address];
|
||||
List<Object?> get props => <Object?>[
|
||||
name,
|
||||
address,
|
||||
placeId,
|
||||
latitude,
|
||||
longitude,
|
||||
city,
|
||||
state,
|
||||
street,
|
||||
country,
|
||||
zipCode,
|
||||
];
|
||||
}
|
||||
|
||||
/// Event triggered to delete a hub.
|
||||
|
||||
@@ -106,9 +106,21 @@ class ClientHubsPage extends StatelessWidget {
|
||||
),
|
||||
if (state.showAddHubDialog)
|
||||
AddHubDialog(
|
||||
onCreate: (String name, String address) {
|
||||
onCreate: (
|
||||
String name,
|
||||
String address, {
|
||||
String? placeId,
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
}) {
|
||||
BlocProvider.of<ClientHubsBloc>(context).add(
|
||||
ClientHubsAddRequested(name: name, address: address),
|
||||
ClientHubsAddRequested(
|
||||
name: name,
|
||||
address: address,
|
||||
placeId: placeId,
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
),
|
||||
);
|
||||
},
|
||||
onCancel: () => BlocProvider.of<ClientHubsBloc>(
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:google_places_flutter/model/prediction.dart';
|
||||
|
||||
import 'hub_address_autocomplete.dart';
|
||||
|
||||
/// A dialog for adding a new hub.
|
||||
class AddHubDialog extends StatefulWidget {
|
||||
/// Callback when the "Create Hub" button is pressed.
|
||||
final Function(String name, String address) onCreate;
|
||||
final void Function(
|
||||
String name,
|
||||
String address, {
|
||||
String? placeId,
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
}) onCreate;
|
||||
|
||||
/// Callback when the dialog is cancelled.
|
||||
final VoidCallback onCancel;
|
||||
@@ -26,18 +33,22 @@ class AddHubDialog extends StatefulWidget {
|
||||
class _AddHubDialogState extends State<AddHubDialog> {
|
||||
late final TextEditingController _nameController;
|
||||
late final TextEditingController _addressController;
|
||||
late final FocusNode _addressFocusNode;
|
||||
Prediction? _selectedPrediction;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_nameController = TextEditingController();
|
||||
_addressController = TextEditingController();
|
||||
_addressFocusNode = FocusNode();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_addressController.dispose();
|
||||
_addressFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -79,6 +90,10 @@ class _AddHubDialogState extends State<AddHubDialog> {
|
||||
HubAddressAutocomplete(
|
||||
controller: _addressController,
|
||||
hintText: t.client_hubs.add_hub_dialog.address_hint,
|
||||
focusNode: _addressFocusNode,
|
||||
onSelected: (Prediction prediction) {
|
||||
_selectedPrediction = prediction;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
Row(
|
||||
@@ -97,6 +112,13 @@ class _AddHubDialogState extends State<AddHubDialog> {
|
||||
widget.onCreate(
|
||||
_nameController.text,
|
||||
_addressController.text,
|
||||
placeId: _selectedPrediction?.placeId,
|
||||
latitude: double.tryParse(
|
||||
_selectedPrediction?.lat ?? '',
|
||||
),
|
||||
longitude: double.tryParse(
|
||||
_selectedPrediction?.lng ?? '',
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9,28 +9,34 @@ class HubAddressAutocomplete extends StatelessWidget {
|
||||
const HubAddressAutocomplete({
|
||||
required this.controller,
|
||||
required this.hintText,
|
||||
this.focusNode,
|
||||
this.onSelected,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final TextEditingController controller;
|
||||
final String hintText;
|
||||
final FocusNode? focusNode;
|
||||
final void Function(Prediction prediction)? onSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GooglePlaceAutoCompleteTextField(
|
||||
textEditingController: controller,
|
||||
focusNode: focusNode,
|
||||
googleAPIKey: HubsConstants.googlePlacesApiKey,
|
||||
debounceTime: 500,
|
||||
countries: HubsConstants.supportedCountries,
|
||||
isLatLngRequired: false,
|
||||
isLatLngRequired: true,
|
||||
getPlaceDetailWithLatLng: (Prediction prediction) {
|
||||
// Handle lat/lng if needed in the future
|
||||
onSelected?.call(prediction);
|
||||
},
|
||||
itemClick: (Prediction prediction) {
|
||||
controller.text = prediction.description ?? '';
|
||||
controller.selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: controller.text.length),
|
||||
);
|
||||
onSelected?.call(prediction);
|
||||
},
|
||||
itemBuilder: (_, _, Prediction prediction) {
|
||||
return Padding(
|
||||
|
||||
@@ -31,6 +31,7 @@ dependencies:
|
||||
firebase_auth: ^6.1.4
|
||||
firebase_data_connect: ^0.2.2+2
|
||||
google_places_flutter: ^2.1.1
|
||||
http: ^1.2.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user