Fix add review route and photo previews
Build and deploy Flutter Web / build (push) Successful in 2m21s

This commit is contained in:
Ruslan Bakiev
2026-06-12 23:06:17 +07:00
parent 44d72dce00
commit 0f5061ce96
3 changed files with 124 additions and 106 deletions
+3 -5
View File
@@ -22,9 +22,9 @@ GoRouter createAppRouter() {
GoRoute( GoRoute(
path: '/', path: '/',
builder: (context, state) => const MapflowShell(), builder: (context, state) => const MapflowShell(),
routes: [ ),
GoRoute( GoRoute(
path: 'experience/new', path: '/experience/new',
pageBuilder: (context, state) { pageBuilder: (context, state) {
final extra = state.extra; final extra = state.extra;
final args = extra is AddExperienceArgs final args = extra is AddExperienceArgs
@@ -53,11 +53,9 @@ GoRouter createAppRouter() {
}, },
), ),
GoRoute( GoRoute(
path: 'admin/reviews', path: '/admin/reviews',
builder: (context, state) => const AdminVoiceExperiencesScreen(), builder: (context, state) => const AdminVoiceExperiencesScreen(),
), ),
], ],
),
],
); );
} }
@@ -320,7 +320,9 @@ class _AddReviewButton extends StatelessWidget {
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
return Padding( return Padding(
padding: const EdgeInsets.only(top: 8, right: 12), padding: const EdgeInsets.only(top: 62, right: 12),
child: SizedBox.square(
dimension: 52,
child: IconButton.filled( child: IconButton.filled(
onPressed: onPressed, onPressed: onPressed,
icon: const Icon(Icons.add_location_alt_outlined), icon: const Icon(Icons.add_location_alt_outlined),
@@ -330,6 +332,7 @@ class _AddReviewButton extends StatelessWidget {
), ),
tooltip: 'Добавить отзыв', tooltip: 'Добавить отзыв',
), ),
),
); );
} }
} }
@@ -1043,8 +1046,7 @@ class _AddExperienceFlowState extends State<AddExperienceFlow> {
final informationProgress = (_informationUnits / _minimumInformationUnits) final informationProgress = (_informationUnits / _minimumInformationUnits)
.clamp(0.0, 1.0); .clamp(0.0, 1.0);
final content = switch (_step) { final content = switch (_step) {
0 => _IntroStep(onNext: () => setState(() => _step = 1)), 0 => _VoiceStep(
1 => _VoiceStep(
placeName: '', placeName: '',
promptHints: _voicePromptHints, promptHints: _voicePromptHints,
hasTelegramAuth: widget.hasTelegramAuth, hasTelegramAuth: widget.hasTelegramAuth,
@@ -1061,11 +1063,11 @@ class _AddExperienceFlowState extends State<AddExperienceFlow> {
} }
setState(() { setState(() {
_nearbyPlacesFuture = _loadNearbyPlaces(); _nearbyPlacesFuture = _loadNearbyPlaces();
_step = 2; _step = 1;
}); });
}, },
), ),
_ => _PlaceStep( 1 => _PlaceStep(
placesFuture: _nearbyPlacesFuture, placesFuture: _nearbyPlacesFuture,
radiusMeters: _nearbyPlaceRadiusMeters, radiusMeters: _nearbyPlaceRadiusMeters,
isSubmitting: _submitting, isSubmitting: _submitting,
@@ -1073,12 +1075,13 @@ class _AddExperienceFlowState extends State<AddExperienceFlow> {
controller.setReviewPlace(place.name); controller.setReviewPlace(place.name);
setState(() { setState(() {
_selectedPlaceForSubmit = place; _selectedPlaceForSubmit = place;
_step = 3; _step = 2;
}); });
}, },
), ),
_ => const SizedBox.shrink(),
}; };
final contentWithFavoriteStep = _step == 3 final contentWithFavoriteStep = _step == 2
? _FavoriteStep( ? _FavoriteStep(
place: _selectedPlaceForSubmit, place: _selectedPlaceForSubmit,
isSubmitting: _submitting, isSubmitting: _submitting,
@@ -1100,7 +1103,7 @@ class _AddExperienceFlowState extends State<AddExperienceFlow> {
children: [ children: [
_StoryProgress( _StoryProgress(
step: _step, step: _step,
total: 4, total: 3,
dark: true, dark: true,
onClose: () => context.pop(), onClose: () => context.pop(),
), ),
@@ -1163,47 +1166,6 @@ class _AddExperienceFlowState extends State<AddExperienceFlow> {
} }
} }
class _IntroStep extends StatelessWidget {
const _IntroStep({required this.onNext});
final VoidCallback onNext;
@override
Widget build(BuildContext context) {
final tokens = context.mapflowTokens;
return _StepLayout(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 70,
height: 70,
decoration: BoxDecoration(
color: tokens.danger,
borderRadius: BorderRadius.circular(tokens.panelRadius),
),
child: Icon(Icons.graphic_eq, color: tokens.onDark, size: 38),
),
const SizedBox(height: 22),
Text(
'Поделись ощущением от места голосом. Нужно быть в заведении или не дальше 50 м. Аудио обработаем и удалим.',
textAlign: TextAlign.left,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: tokens.onDark,
fontWeight: FontWeight.w800,
height: 1.18,
letterSpacing: 0,
),
),
],
),
action: FilledButton(onPressed: onNext, child: const Text('Далее')),
);
}
}
class _PlaceStep extends StatelessWidget { class _PlaceStep extends StatelessWidget {
const _PlaceStep({ const _PlaceStep({
required this.placesFuture, required this.placesFuture,
@@ -1792,17 +1754,15 @@ class _VoiceRecordButtonState extends State<_VoiceRecordButton>
} }
class _StepLayout extends StatelessWidget { class _StepLayout extends StatelessWidget {
const _StepLayout({required this.body, this.action}); const _StepLayout({required this.body});
final Widget body; final Widget body;
final Widget? action;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
children: [ children: [
Expanded(child: body), Expanded(child: body),
if (action != null) SizedBox(width: double.infinity, child: action),
], ],
); );
} }
@@ -19,11 +19,12 @@ class PlacePhotoCard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final tokens = context.mapflowTokens; final tokens = context.mapflowTokens;
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
final photoUrls = place.photoUrls.take(3).toList();
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
width: 150, width: 226,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(tokens.panelRadius), borderRadius: BorderRadius.circular(tokens.panelRadius),
border: Border.all(color: colorScheme.surface), border: Border.all(color: colorScheme.surface),
@@ -39,24 +40,15 @@ class PlacePhotoCard extends StatelessWidget {
child: Stack( child: Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
if (place.photoUrls.isEmpty) if (photoUrls.isEmpty)
ColoredBox( ColoredBox(
color: colorScheme.primary, color: colorScheme.primary,
child: Icon(Icons.place_outlined, color: colorScheme.onPrimary), child: Icon(Icons.place_outlined, color: colorScheme.onPrimary),
) )
else else
PageView.builder( _PhotoTriptych(
itemCount: place.photoUrls.length, photoUrls: photoUrls,
itemBuilder: (context, index) { fallbackColor: tokens.mapPanelBorder,
return Image.network(
place.photoUrls[index],
fit: BoxFit.cover,
errorBuilder: (_, _, _) => ColoredBox(
color: tokens.mapPanelBorder,
child: const Icon(Icons.place_outlined),
),
);
},
), ),
DecoratedBox( DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -105,3 +97,71 @@ class PlacePhotoCard extends StatelessWidget {
); );
} }
} }
class _PhotoTriptych extends StatelessWidget {
const _PhotoTriptych({
required this.photoUrls,
required this.fallbackColor,
});
final List<String> photoUrls;
final Color fallbackColor;
@override
Widget build(BuildContext context) {
if (photoUrls.length == 1) {
return _PhotoTile(url: photoUrls.first, fallbackColor: fallbackColor);
}
return Row(
children: [
Expanded(
flex: 3,
child: _PhotoTile(url: photoUrls[0], fallbackColor: fallbackColor),
),
const SizedBox(width: 2),
Expanded(
flex: 2,
child: Column(
children: [
Expanded(
child: _PhotoTile(
url: photoUrls[1],
fallbackColor: fallbackColor,
),
),
if (photoUrls.length > 2) ...[
const SizedBox(height: 2),
Expanded(
child: _PhotoTile(
url: photoUrls[2],
fallbackColor: fallbackColor,
),
),
],
],
),
),
],
);
}
}
class _PhotoTile extends StatelessWidget {
const _PhotoTile({required this.url, required this.fallbackColor});
final String url;
final Color fallbackColor;
@override
Widget build(BuildContext context) {
return Image.network(
url,
fit: BoxFit.cover,
errorBuilder: (_, _, _) => ColoredBox(
color: fallbackColor,
child: const Icon(Icons.place_outlined),
),
);
}
}