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(
path: '/',
builder: (context, state) => const MapflowShell(),
routes: [
),
GoRoute(
path: 'experience/new',
path: '/experience/new',
pageBuilder: (context, state) {
final extra = state.extra;
final args = extra is AddExperienceArgs
@@ -53,11 +53,9 @@ GoRouter createAppRouter() {
},
),
GoRoute(
path: 'admin/reviews',
path: '/admin/reviews',
builder: (context, state) => const AdminVoiceExperiencesScreen(),
),
],
),
],
);
}
@@ -320,7 +320,9 @@ class _AddReviewButton extends StatelessWidget {
final colorScheme = Theme.of(context).colorScheme;
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(
onPressed: onPressed,
icon: const Icon(Icons.add_location_alt_outlined),
@@ -330,6 +332,7 @@ class _AddReviewButton extends StatelessWidget {
),
tooltip: 'Добавить отзыв',
),
),
);
}
}
@@ -1043,8 +1046,7 @@ class _AddExperienceFlowState extends State<AddExperienceFlow> {
final informationProgress = (_informationUnits / _minimumInformationUnits)
.clamp(0.0, 1.0);
final content = switch (_step) {
0 => _IntroStep(onNext: () => setState(() => _step = 1)),
1 => _VoiceStep(
0 => _VoiceStep(
placeName: '',
promptHints: _voicePromptHints,
hasTelegramAuth: widget.hasTelegramAuth,
@@ -1061,11 +1063,11 @@ class _AddExperienceFlowState extends State<AddExperienceFlow> {
}
setState(() {
_nearbyPlacesFuture = _loadNearbyPlaces();
_step = 2;
_step = 1;
});
},
),
_ => _PlaceStep(
1 => _PlaceStep(
placesFuture: _nearbyPlacesFuture,
radiusMeters: _nearbyPlaceRadiusMeters,
isSubmitting: _submitting,
@@ -1073,12 +1075,13 @@ class _AddExperienceFlowState extends State<AddExperienceFlow> {
controller.setReviewPlace(place.name);
setState(() {
_selectedPlaceForSubmit = place;
_step = 3;
_step = 2;
});
},
),
_ => const SizedBox.shrink(),
};
final contentWithFavoriteStep = _step == 3
final contentWithFavoriteStep = _step == 2
? _FavoriteStep(
place: _selectedPlaceForSubmit,
isSubmitting: _submitting,
@@ -1100,7 +1103,7 @@ class _AddExperienceFlowState extends State<AddExperienceFlow> {
children: [
_StoryProgress(
step: _step,
total: 4,
total: 3,
dark: true,
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 {
const _PlaceStep({
required this.placesFuture,
@@ -1792,17 +1754,15 @@ class _VoiceRecordButtonState extends State<_VoiceRecordButton>
}
class _StepLayout extends StatelessWidget {
const _StepLayout({required this.body, this.action});
const _StepLayout({required this.body});
final Widget body;
final Widget? action;
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(child: body),
if (action != null) SizedBox(width: double.infinity, child: action),
],
);
}
@@ -19,11 +19,12 @@ class PlacePhotoCard extends StatelessWidget {
Widget build(BuildContext context) {
final tokens = context.mapflowTokens;
final colorScheme = Theme.of(context).colorScheme;
final photoUrls = place.photoUrls.take(3).toList();
return GestureDetector(
onTap: onTap,
child: Container(
width: 150,
width: 226,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(tokens.panelRadius),
border: Border.all(color: colorScheme.surface),
@@ -39,24 +40,15 @@ class PlacePhotoCard extends StatelessWidget {
child: Stack(
fit: StackFit.expand,
children: [
if (place.photoUrls.isEmpty)
if (photoUrls.isEmpty)
ColoredBox(
color: colorScheme.primary,
child: Icon(Icons.place_outlined, color: colorScheme.onPrimary),
)
else
PageView.builder(
itemCount: place.photoUrls.length,
itemBuilder: (context, index) {
return Image.network(
place.photoUrls[index],
fit: BoxFit.cover,
errorBuilder: (_, _, _) => ColoredBox(
color: tokens.mapPanelBorder,
child: const Icon(Icons.place_outlined),
),
);
},
_PhotoTriptych(
photoUrls: photoUrls,
fallbackColor: tokens.mapPanelBorder,
),
DecoratedBox(
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),
),
);
}
}