Show Google place types in selection cards
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m11s

This commit is contained in:
Ruslan Bakiev
2026-05-13 22:24:20 +07:00
parent 29e856bbd8
commit 28e8cee6e6
3 changed files with 127 additions and 15 deletions

View File

@@ -160,6 +160,8 @@ class MapflowApi {
name
latitude
longitude
googlePrimaryType
googleTypes
experiences {
id
status
@@ -184,6 +186,10 @@ class MapflowApi {
(place['longitude'] as num).toDouble(),
),
traits: _traitsFromExperiences(place['experiences'] as List<dynamic>),
googlePrimaryType: place['googlePrimaryType'] as String?,
googleTypes: (place['googleTypes'] as List<dynamic>)
.map((type) => type as String)
.toList(),
);
}).toList();
}
@@ -201,6 +207,8 @@ class MapflowApi {
name
latitude
longitude
googlePrimaryType
googleTypes
experiences {
id
status
@@ -233,6 +241,10 @@ class MapflowApi {
(place['longitude'] as num).toDouble(),
),
traits: _traitsFromExperiences(place['experiences'] as List<dynamic>),
googlePrimaryType: place['googlePrimaryType'] as String?,
googleTypes: (place['googleTypes'] as List<dynamic>)
.map((type) => type as String)
.toList(),
);
}).toList();
}

View File

@@ -90,6 +90,8 @@ class PlaceRecommendation {
required this.photoUrls,
required this.coordinate,
required this.traits,
required this.googlePrimaryType,
required this.googleTypes,
});
final String id;
@@ -99,6 +101,8 @@ class PlaceRecommendation {
final List<String> photoUrls;
final LatLng coordinate;
final Set<PlaceTrait> traits;
final String? googlePrimaryType;
final List<String> googleTypes;
String get coverPhotoUrl => photoUrls.first;
}

View File

@@ -963,21 +963,10 @@ class _PlaceStep extends StatelessWidget {
separatorBuilder: (_, _) => const SizedBox(height: 10),
itemBuilder: (context, index) {
final place = places[index];
return ListTile(
onTap: isSubmitting ? null : () => onSelect(place),
contentPadding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 8,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
tileColor: const Color(0xFFFFFBF5),
leading: const Icon(Icons.place_outlined),
title: Text(
place.name,
style: const TextStyle(fontWeight: FontWeight.w800),
),
return _NearbyPlaceCard(
place: place,
disabled: isSubmitting,
onTap: () => onSelect(place),
);
},
);
@@ -990,6 +979,113 @@ class _PlaceStep extends StatelessWidget {
}
}
class _NearbyPlaceCard extends StatelessWidget {
const _NearbyPlaceCard({
required this.place,
required this.disabled,
required this.onTap,
});
final PlaceRecommendation place;
final bool disabled;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final primaryType = _formatType(place.googlePrimaryType);
final visibleTypes = place.googleTypes
.where((type) => type != place.googlePrimaryType)
.map(_formatType)
.nonNulls
.take(4)
.toList();
return Material(
color: const Color(0xFFFFFBF5),
borderRadius: BorderRadius.circular(8),
child: InkWell(
onTap: disabled ? null : onTap,
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.all(14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(Icons.place_outlined),
const SizedBox(width: 10),
Expanded(
child: Text(
place.name,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.w900,
height: 1.1,
),
),
),
],
),
if (primaryType != null || visibleTypes.isNotEmpty) ...[
const SizedBox(height: 12),
Wrap(
spacing: 6,
runSpacing: 6,
children: [
if (primaryType != null)
_PlaceTypeChip(label: primaryType, primary: true),
for (final type in visibleTypes)
_PlaceTypeChip(label: type, primary: false),
],
),
],
],
),
),
),
);
}
String? _formatType(String? type) {
if (type == null || type.isEmpty) {
return null;
}
return type.replaceAll('_', ' ');
}
}
class _PlaceTypeChip extends StatelessWidget {
const _PlaceTypeChip({required this.label, required this.primary});
final String label;
final bool primary;
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
color: primary ? const Color(0xFF111827) : const Color(0xFFECE5D8),
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
child: Text(
label,
style: TextStyle(
color: primary ? Colors.white : const Color(0xFF3A332A),
fontSize: 12,
fontWeight: FontWeight.w700,
height: 1,
),
),
),
);
}
}
class _VoiceStep extends StatelessWidget {
const _VoiceStep({
required this.placeName,