This commit is contained in:
@@ -73,6 +73,7 @@ class _MapContentState extends State<_MapContent> {
|
||||
final selected = state.selectedPlace;
|
||||
final userCoordinate = state.userCoordinate;
|
||||
final targetCenter = _focusCenter(state);
|
||||
final reviewCoordinate = userCoordinate ?? selected?.coordinate;
|
||||
final placeCubit = context.read<PlaceCubit>();
|
||||
final traitCounts = _countPlaceTraits(state.places);
|
||||
final availableTraits = [
|
||||
@@ -98,7 +99,6 @@ class _MapContentState extends State<_MapContent> {
|
||||
userCoordinate: userCoordinate,
|
||||
onPlaceTap: (place) => _selectAndShowPlace(context, place),
|
||||
),
|
||||
if (targetCenter == null) const _MapTargetLoadingOverlay(),
|
||||
SafeArea(
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
@@ -118,10 +118,9 @@ class _MapContentState extends State<_MapContent> {
|
||||
child: Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: _AddReviewAction(
|
||||
onPressed: () => _openAddFlow(
|
||||
context,
|
||||
userCoordinate ?? selected?.coordinate,
|
||||
),
|
||||
onPressed: reviewCoordinate == null
|
||||
? null
|
||||
: () => _openAddFlow(context, reviewCoordinate),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -614,7 +613,7 @@ class _UserAvatar extends StatelessWidget {
|
||||
class _AddReviewAction extends StatelessWidget {
|
||||
const _AddReviewAction({required this.onPressed});
|
||||
|
||||
final VoidCallback onPressed;
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -1515,49 +1514,6 @@ class _MapLoading extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _MapTargetLoadingOverlay extends StatelessWidget {
|
||||
const _MapTargetLoadingOverlay();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = context.mapflowTokens;
|
||||
return IgnorePointer(
|
||||
child: SafeArea(
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 18),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: tokens.mapPanel.withValues(alpha: 0.84),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.24),
|
||||
blurRadius: 22,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: SizedBox.square(
|
||||
dimension: 28,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.5,
|
||||
color: tokens.voiceAccent,
|
||||
backgroundColor: tokens.onDark.withValues(alpha: 0.12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MapError extends StatelessWidget {
|
||||
const _MapError({required this.message});
|
||||
|
||||
@@ -2385,6 +2341,7 @@ class AddExperienceFlow extends StatefulWidget {
|
||||
class _AddExperienceFlowState extends State<AddExperienceFlow> {
|
||||
static const _minimumInformationUnits = 16.0;
|
||||
static const _nearbyPlaceRadiusMeters = 50;
|
||||
static const _nearbyPlacesTimeout = Duration(seconds: 12);
|
||||
static const _voicePromptHints = [
|
||||
'атмосфера',
|
||||
'еда',
|
||||
@@ -2445,10 +2402,13 @@ class _AddExperienceFlowState extends State<AddExperienceFlow> {
|
||||
return const [];
|
||||
}
|
||||
|
||||
return context.read<PlacesRepository>().fetchNearbyPlaces(
|
||||
coordinate: coordinate,
|
||||
radiusMeters: _nearbyPlaceRadiusMeters,
|
||||
);
|
||||
return context
|
||||
.read<PlacesRepository>()
|
||||
.fetchNearbyPlaces(
|
||||
coordinate: coordinate,
|
||||
radiusMeters: _nearbyPlaceRadiusMeters,
|
||||
)
|
||||
.timeout(_nearbyPlacesTimeout);
|
||||
}
|
||||
|
||||
Future<void> _startRecording() async {
|
||||
@@ -2559,8 +2519,14 @@ class _AddExperienceFlowState extends State<AddExperienceFlow> {
|
||||
),
|
||||
1 => _PlaceStep(
|
||||
placesFuture: _nearbyPlacesFuture,
|
||||
hasCoordinate: widget.coordinate != null,
|
||||
radiusMeters: _nearbyPlaceRadiusMeters,
|
||||
isSubmitting: _submitting,
|
||||
onRetry: () {
|
||||
setState(() {
|
||||
_nearbyPlacesFuture = _loadNearbyPlaces();
|
||||
});
|
||||
},
|
||||
onSelect: (place) async {
|
||||
controller.setReviewPlace(place.name);
|
||||
setState(() {
|
||||
@@ -2659,14 +2625,18 @@ class _AddExperienceFlowState extends State<AddExperienceFlow> {
|
||||
class _PlaceStep extends StatelessWidget {
|
||||
const _PlaceStep({
|
||||
required this.placesFuture,
|
||||
required this.hasCoordinate,
|
||||
required this.radiusMeters,
|
||||
required this.isSubmitting,
|
||||
required this.onRetry,
|
||||
required this.onSelect,
|
||||
});
|
||||
|
||||
final Future<List<PlaceRecommendation>>? placesFuture;
|
||||
final bool hasCoordinate;
|
||||
final int radiusMeters;
|
||||
final bool isSubmitting;
|
||||
final VoidCallback onRetry;
|
||||
final Future<void> Function(PlaceRecommendation) onSelect;
|
||||
|
||||
@override
|
||||
@@ -2687,59 +2657,60 @@ class _PlaceStep extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: FutureBuilder<List<PlaceRecommendation>>(
|
||||
future: placesFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState != ConnectionState.done) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(color: tokens.voiceAccent),
|
||||
);
|
||||
}
|
||||
if (snapshot.hasError) {
|
||||
return Center(
|
||||
child: Icon(
|
||||
Icons.error_outline,
|
||||
color: tokens.onDark,
|
||||
size: 42,
|
||||
),
|
||||
);
|
||||
}
|
||||
child: placesFuture == null
|
||||
? _PlaceUnavailable(
|
||||
icon: Icons.location_off_outlined,
|
||||
message: 'Нет геопозиции',
|
||||
tokens: tokens,
|
||||
)
|
||||
: FutureBuilder<List<PlaceRecommendation>>(
|
||||
future: placesFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState != ConnectionState.done) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: tokens.voiceAccent,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (snapshot.hasError) {
|
||||
return _PlaceUnavailable(
|
||||
icon: Icons.error_outline,
|
||||
message: 'Места не загрузились',
|
||||
tokens: tokens,
|
||||
action: TextButton(
|
||||
onPressed: onRetry,
|
||||
child: const Text('Повторить'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final places = snapshot.data ?? const <PlaceRecommendation>[];
|
||||
if (places.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.location_off_outlined,
|
||||
color: tokens.onDark,
|
||||
size: 42,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'Нет мест в $radiusMetersм',
|
||||
style: TextStyle(color: tokens.onDark),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
final places =
|
||||
snapshot.data ?? const <PlaceRecommendation>[];
|
||||
if (places.isEmpty) {
|
||||
return _PlaceUnavailable(
|
||||
icon: Icons.location_off_outlined,
|
||||
message: hasCoordinate
|
||||
? 'Нет мест в $radiusMetersм'
|
||||
: 'Нет геопозиции',
|
||||
tokens: tokens,
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
itemCount: places.length,
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 10),
|
||||
itemBuilder: (context, index) {
|
||||
final place = places[index];
|
||||
return _NearbyPlaceCard(
|
||||
place: place,
|
||||
disabled: isSubmitting,
|
||||
onTap: () => onSelect(place),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
return ListView.separated(
|
||||
itemCount: places.length,
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 10),
|
||||
itemBuilder: (context, index) {
|
||||
final place = places[index];
|
||||
return _NearbyPlaceCard(
|
||||
place: place,
|
||||
disabled: isSubmitting,
|
||||
onTap: () => onSelect(place),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -2747,6 +2718,35 @@ class _PlaceStep extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _PlaceUnavailable extends StatelessWidget {
|
||||
const _PlaceUnavailable({
|
||||
required this.icon,
|
||||
required this.message,
|
||||
required this.tokens,
|
||||
this.action,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
final String message;
|
||||
final MapflowThemeTokens tokens;
|
||||
final Widget? action;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, color: tokens.onDark, size: 42),
|
||||
const SizedBox(height: 10),
|
||||
Text(message, style: TextStyle(color: tokens.onDark)),
|
||||
if (action != null) ...[const SizedBox(height: 8), action!],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FavoriteStep extends StatelessWidget {
|
||||
const _FavoriteStep({
|
||||
required this.place,
|
||||
|
||||
Reference in New Issue
Block a user