This commit is contained in:
@@ -223,17 +223,31 @@ class _MapContent extends StatelessWidget {
|
||||
|
||||
void _selectAndShowPlace(BuildContext context, PlaceRecommendation place) {
|
||||
final placeCubit = context.read<PlaceCubit>()..selectPlace(place.id);
|
||||
final placeDetails = placeCubit.loadPlaceDetails(place);
|
||||
unawaited(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
isDismissible: true,
|
||||
enableDrag: true,
|
||||
showDragHandle: true,
|
||||
builder: (sheetContext) {
|
||||
return _PlaceDetailsSheet(
|
||||
place: place,
|
||||
onFavoriteToggle: () => placeCubit.toggleFavorite(place),
|
||||
onPhotoTap: (index) =>
|
||||
_openPhotoViewer(sheetContext, place.photoUrls, index),
|
||||
return DraggableScrollableSheet(
|
||||
expand: false,
|
||||
initialChildSize: 0.64,
|
||||
minChildSize: 0.24,
|
||||
maxChildSize: 0.9,
|
||||
builder: (_, scrollController) {
|
||||
return _PlaceDetailsSheet(
|
||||
place: place,
|
||||
details: placeDetails,
|
||||
scrollController: scrollController,
|
||||
onFavoriteToggle: (selectedPlace) =>
|
||||
placeCubit.toggleFavorite(selectedPlace),
|
||||
onPhotoTap: (photoUrls, index) =>
|
||||
_openPhotoViewer(sheetContext, photoUrls, index),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -1094,13 +1108,17 @@ class _PlaceCarousel extends StatelessWidget {
|
||||
class _PlaceDetailsSheet extends StatefulWidget {
|
||||
const _PlaceDetailsSheet({
|
||||
required this.place,
|
||||
required this.details,
|
||||
required this.scrollController,
|
||||
required this.onFavoriteToggle,
|
||||
required this.onPhotoTap,
|
||||
});
|
||||
|
||||
final PlaceRecommendation place;
|
||||
final VoidCallback onFavoriteToggle;
|
||||
final ValueChanged<int> onPhotoTap;
|
||||
final Future<PlaceRecommendation> details;
|
||||
final ScrollController scrollController;
|
||||
final ValueChanged<PlaceRecommendation> onFavoriteToggle;
|
||||
final void Function(List<String> photoUrls, int index) onPhotoTap;
|
||||
|
||||
@override
|
||||
State<_PlaceDetailsSheet> createState() => _PlaceDetailsSheetState();
|
||||
@@ -1114,93 +1132,110 @@ class _PlaceDetailsSheetState extends State<_PlaceDetailsSheet> {
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = context.mapflowTokens;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final openingSummary = _openingSummary(widget.place);
|
||||
final weekdayDescriptions = _weekdayDescriptions(widget.place);
|
||||
final typeLabel = _placeTypeLabel(widget.place);
|
||||
return FutureBuilder<PlaceRecommendation>(
|
||||
future: widget.details,
|
||||
builder: (context, snapshot) {
|
||||
final place = snapshot.data ?? widget.place;
|
||||
final loadingDetails =
|
||||
snapshot.connectionState != ConnectionState.done &&
|
||||
place.photoUrls.isEmpty;
|
||||
final openingSummary = _openingSummary(place);
|
||||
final weekdayDescriptions = _weekdayDescriptions(place);
|
||||
final typeLabel = _placeTypeLabel(place);
|
||||
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.sizeOf(context).height * 0.86,
|
||||
),
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 18),
|
||||
children: [
|
||||
_PlaceDetailsPhotos(
|
||||
photoUrls: widget.place.photoUrls,
|
||||
onPhotoTap: widget.onPhotoTap,
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.place.name,
|
||||
style: Theme.of(context).textTheme.headlineSmall
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
),
|
||||
if (typeLabel != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
typeLabel,
|
||||
style: Theme.of(context).textTheme.bodyMedium
|
||||
?.copyWith(color: colorScheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
onPressed: _toggleFavorite,
|
||||
icon: Icon(
|
||||
_isFavorite ? Icons.favorite : Icons.favorite_border,
|
||||
),
|
||||
tooltip: 'Избранное',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (openingSummary != null || weekdayDescriptions.isNotEmpty)
|
||||
_OpeningHoursTile(
|
||||
summary: openingSummary,
|
||||
weekdayDescriptions: weekdayDescriptions,
|
||||
expanded: _hoursExpanded,
|
||||
onExpansionChanged: (value) =>
|
||||
setState(() => _hoursExpanded = value),
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: ListView(
|
||||
controller: widget.scrollController,
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 18),
|
||||
children: [
|
||||
_PlaceDetailsPhotos(
|
||||
photoUrls: place.photoUrls,
|
||||
loading: loadingDetails,
|
||||
onPhotoTap: (index) =>
|
||||
widget.onPhotoTap(place.photoUrls, index),
|
||||
),
|
||||
if (widget.place.traits.isNotEmpty) ...[
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
const SizedBox(height: 14),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
for (final trait in widget.place.traits)
|
||||
Chip(
|
||||
avatar: Icon(trait.icon, size: 16),
|
||||
label: Text(trait.label),
|
||||
side: BorderSide(color: tokens.mapPanelBorder),
|
||||
backgroundColor: tokens.mapPanel,
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
place.name,
|
||||
style: Theme.of(context).textTheme.headlineSmall
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
),
|
||||
if (typeLabel != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
typeLabel,
|
||||
style: Theme.of(context).textTheme.bodyMedium
|
||||
?.copyWith(color: colorScheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
onPressed: _toggleFavorite,
|
||||
icon: Icon(
|
||||
_isFavorite ? Icons.favorite : Icons.favorite_border,
|
||||
),
|
||||
tooltip: 'Избранное',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (snapshot.hasError) ...[
|
||||
Text(
|
||||
'Не удалось загрузить детали заведения',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.error,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
if (openingSummary != null || weekdayDescriptions.isNotEmpty)
|
||||
_OpeningHoursTile(
|
||||
summary: openingSummary,
|
||||
weekdayDescriptions: weekdayDescriptions,
|
||||
expanded: _hoursExpanded,
|
||||
onExpansionChanged: (value) =>
|
||||
setState(() => _hoursExpanded = value),
|
||||
),
|
||||
if (place.traits.isNotEmpty) ...[
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
for (final trait in place.traits)
|
||||
Chip(
|
||||
avatar: Icon(trait.icon, size: 16),
|
||||
label: Text(trait.label),
|
||||
side: BorderSide(color: tokens.mapPanelBorder),
|
||||
backgroundColor: tokens.mapPanel,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _toggleFavorite() {
|
||||
setState(() => _isFavorite = !_isFavorite);
|
||||
widget.onFavoriteToggle();
|
||||
widget.onFavoriteToggle(widget.place);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1274,10 +1309,12 @@ class _OpeningHoursTile extends StatelessWidget {
|
||||
class _PlaceDetailsPhotos extends StatelessWidget {
|
||||
const _PlaceDetailsPhotos({
|
||||
required this.photoUrls,
|
||||
required this.loading,
|
||||
required this.onPhotoTap,
|
||||
});
|
||||
|
||||
final List<String> photoUrls;
|
||||
final bool loading;
|
||||
final ValueChanged<int> onPhotoTap;
|
||||
|
||||
@override
|
||||
@@ -1292,10 +1329,20 @@ class _PlaceDetailsPhotos extends StatelessWidget {
|
||||
color: tokens.mapPanelBorder,
|
||||
borderRadius: BorderRadius.circular(tokens.panelRadius),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.place_outlined,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
size: 40,
|
||||
child: Center(
|
||||
child: loading
|
||||
? SizedBox.square(
|
||||
dimension: 28,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.4,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
Icons.place_outlined,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user