Use nearby Google places for reviews
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 1m48s
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 1m48s
This commit is contained in:
@@ -191,6 +191,55 @@ class MapflowApi {
|
|||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<PlaceRecommendation>> fetchNearbyPlaces({
|
||||||
|
required LatLng coordinate,
|
||||||
|
required int radiusMeters,
|
||||||
|
}) async {
|
||||||
|
final data = await _graphql(
|
||||||
|
'''
|
||||||
|
query NearbyPlaces(\$input: NearbyPlacesInput!) {
|
||||||
|
nearbyPlaces(input: \$input) {
|
||||||
|
id
|
||||||
|
googlePlaceId
|
||||||
|
name
|
||||||
|
latitude
|
||||||
|
longitude
|
||||||
|
experiences {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
analysis
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
variables: {
|
||||||
|
'input': {
|
||||||
|
'latitude': coordinate.latitude,
|
||||||
|
'longitude': coordinate.longitude,
|
||||||
|
'radiusMeters': radiusMeters,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final places = data['nearbyPlaces'] as List<dynamic>;
|
||||||
|
return places.map((item) {
|
||||||
|
final place = item as Map<String, dynamic>;
|
||||||
|
return PlaceRecommendation(
|
||||||
|
id: place['id'] as String,
|
||||||
|
googlePlaceId: place['googlePlaceId'] as String,
|
||||||
|
name: place['name'] as String,
|
||||||
|
area: '',
|
||||||
|
photoUrls: const [],
|
||||||
|
coordinate: LatLng(
|
||||||
|
(place['latitude'] as num).toDouble(),
|
||||||
|
(place['longitude'] as num).toDouble(),
|
||||||
|
),
|
||||||
|
traits: _traitsFromExperiences(place['experiences'] as List<dynamic>),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> createVoiceExperience({
|
Future<void> createVoiceExperience({
|
||||||
required String googlePlaceId,
|
required String googlePlaceId,
|
||||||
required String googleName,
|
required String googleName,
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ class _MapContent extends ConsumerWidget {
|
|||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
builder: (_) => AddExperienceFlow(
|
builder: (_) => AddExperienceFlow(
|
||||||
coordinate: coordinate ?? _fallbackCenter,
|
coordinate: coordinate,
|
||||||
hasTelegramAuth: state.hasTelegramAuth,
|
hasTelegramAuth: state.hasTelegramAuth,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -711,7 +711,7 @@ class AddExperienceFlow extends ConsumerStatefulWidget {
|
|||||||
required this.hasTelegramAuth,
|
required this.hasTelegramAuth,
|
||||||
});
|
});
|
||||||
|
|
||||||
final LatLng coordinate;
|
final LatLng? coordinate;
|
||||||
final bool hasTelegramAuth;
|
final bool hasTelegramAuth;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -720,10 +720,13 @@ class AddExperienceFlow extends ConsumerStatefulWidget {
|
|||||||
|
|
||||||
class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||||
static const _minimumVoiceSeconds = 30;
|
static const _minimumVoiceSeconds = 30;
|
||||||
|
static const _nearbyPlaceRadiusMeters = 200;
|
||||||
|
|
||||||
|
final _api = MapflowApi();
|
||||||
final _recorder = AudioRecorder();
|
final _recorder = AudioRecorder();
|
||||||
final _waveSamples = List<double>.filled(64, 0.04);
|
final _waveSamples = List<double>.filled(64, 0.04);
|
||||||
|
|
||||||
|
late final Future<List<PlaceRecommendation>> _nearbyPlacesFuture;
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
StreamSubscription<Uint8List>? _audioStreamSub;
|
StreamSubscription<Uint8List>? _audioStreamSub;
|
||||||
var _step = 0;
|
var _step = 0;
|
||||||
@@ -736,6 +739,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_nearbyPlacesFuture = _loadNearbyPlaces();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -755,6 +759,18 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
await _startRecording();
|
await _startRecording();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<PlaceRecommendation>> _loadNearbyPlaces() async {
|
||||||
|
final coordinate = widget.coordinate;
|
||||||
|
if (coordinate == null) {
|
||||||
|
return const [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return _api.fetchNearbyPlaces(
|
||||||
|
coordinate: coordinate,
|
||||||
|
radiusMeters: _nearbyPlaceRadiusMeters,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _startRecording() async {
|
Future<void> _startRecording() async {
|
||||||
final hasPermission = await _recorder.hasPermission();
|
final hasPermission = await _recorder.hasPermission();
|
||||||
if (!hasPermission) {
|
if (!hasPermission) {
|
||||||
@@ -833,12 +849,11 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final controller = ref.read(placeControllerProvider.notifier);
|
final controller = ref.read(placeControllerProvider.notifier);
|
||||||
final places = ref.watch(placeControllerProvider).value?.places ?? [];
|
|
||||||
|
|
||||||
final content = switch (_step) {
|
final content = switch (_step) {
|
||||||
0 => _IntroStep(onNext: () => setState(() => _step = 1)),
|
0 => _IntroStep(onNext: () => setState(() => _step = 1)),
|
||||||
1 => _PlaceStep(
|
1 => _PlaceStep(
|
||||||
places: places,
|
placesFuture: _nearbyPlacesFuture,
|
||||||
|
radiusMeters: _nearbyPlaceRadiusMeters,
|
||||||
onSelect: (place) {
|
onSelect: (place) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedPlace = place;
|
_selectedPlace = place;
|
||||||
@@ -949,9 +964,14 @@ class _IntroStep extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _PlaceStep extends StatelessWidget {
|
class _PlaceStep extends StatelessWidget {
|
||||||
const _PlaceStep({required this.places, required this.onSelect});
|
const _PlaceStep({
|
||||||
|
required this.placesFuture,
|
||||||
|
required this.radiusMeters,
|
||||||
|
required this.onSelect,
|
||||||
|
});
|
||||||
|
|
||||||
final List<PlaceRecommendation> places;
|
final Future<List<PlaceRecommendation>> placesFuture;
|
||||||
|
final int radiusMeters;
|
||||||
final ValueChanged<PlaceRecommendation> onSelect;
|
final ValueChanged<PlaceRecommendation> onSelect;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -969,11 +989,33 @@ class _PlaceStep extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: places.isEmpty
|
child: FutureBuilder<List<PlaceRecommendation>>(
|
||||||
? const Center(
|
future: placesFuture,
|
||||||
child: Icon(Icons.location_off_outlined, size: 42),
|
builder: (context, snapshot) {
|
||||||
)
|
if (snapshot.connectionState != ConnectionState.done) {
|
||||||
: ListView.separated(
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return const Center(
|
||||||
|
child: Icon(Icons.error_outline, size: 42),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final places = snapshot.data ?? const <PlaceRecommendation>[];
|
||||||
|
if (places.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.location_off_outlined, size: 42),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text('Нет мест в $radiusMetersм'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.separated(
|
||||||
itemCount: places.length,
|
itemCount: places.length,
|
||||||
separatorBuilder: (_, _) => const SizedBox(height: 10),
|
separatorBuilder: (_, _) => const SizedBox(height: 10),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
@@ -995,6 +1037,8 @@ class _PlaceStep extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class PlaceState {
|
|||||||
});
|
});
|
||||||
|
|
||||||
static final _distance = Distance();
|
static final _distance = Distance();
|
||||||
|
static const nearbyRecommendationRadiusMeters = 500.0;
|
||||||
|
|
||||||
final PlaceTrait selectedTrait;
|
final PlaceTrait selectedTrait;
|
||||||
final List<PlaceRecommendation> places;
|
final List<PlaceRecommendation> places;
|
||||||
@@ -30,7 +31,17 @@ class PlaceState {
|
|||||||
final VoiceReviewDraft reviewDraft;
|
final VoiceReviewDraft reviewDraft;
|
||||||
|
|
||||||
List<PlaceRecommendation> get recommendations {
|
List<PlaceRecommendation> get recommendations {
|
||||||
final ranked = [...places]
|
final coordinate = userCoordinate;
|
||||||
|
if (coordinate == null) {
|
||||||
|
return const [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final nearbyPlaces = places.where((place) {
|
||||||
|
return _distance(coordinate, place.coordinate) <=
|
||||||
|
nearbyRecommendationRadiusMeters;
|
||||||
|
});
|
||||||
|
|
||||||
|
final ranked = [...nearbyPlaces]
|
||||||
..sort((a, b) {
|
..sort((a, b) {
|
||||||
final aScore = a.traits.contains(selectedTrait) ? 1 : 0;
|
final aScore = a.traits.contains(selectedTrait) ? 1 : 0;
|
||||||
final bScore = b.traits.contains(selectedTrait) ? 1 : 0;
|
final bScore = b.traits.contains(selectedTrait) ? 1 : 0;
|
||||||
@@ -39,11 +50,6 @@ class PlaceState {
|
|||||||
return scoreOrder;
|
return scoreOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
final coordinate = userCoordinate;
|
|
||||||
if (coordinate == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _distance(
|
return _distance(
|
||||||
coordinate,
|
coordinate,
|
||||||
a.coordinate,
|
a.coordinate,
|
||||||
|
|||||||
Reference in New Issue
Block a user