Add Widgetbook and theme component tokens
Build and deploy Flutter Web / build (push) Successful in 2m42s

This commit is contained in:
Ruslan Bakiev
2026-06-12 14:36:14 +07:00
parent 5e78a134b0
commit 2853cc2581
18 changed files with 1352 additions and 93 deletions
@@ -2,6 +2,7 @@ import 'dart:math' as math;
import 'package:flutter/material.dart';
import '../../../app/theme/mapflow_theme.dart';
import '../data/mapflow_api.dart';
import '../domain/place_models.dart';
@@ -20,8 +21,10 @@ class _AdminVoiceExperiencesScreenState
@override
Widget build(BuildContext context) {
final tokens = context.mapflowTokens;
return Scaffold(
backgroundColor: const Color(0xFFFFFBF5),
backgroundColor: tokens.mapPanel,
appBar: AppBar(title: const Text('Отзывы')),
body: FutureBuilder<List<VoiceExperienceDebug>>(
future: _future,
@@ -56,10 +59,11 @@ class _AdminVoiceExperienceRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final selectedTags = _selectedAdminTags(review.analysis);
final tokens = context.mapflowTokens;
return Material(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(tokens.panelRadius),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
@@ -86,7 +90,7 @@ class _AdminVoiceExperienceRow extends StatelessWidget {
'${review.userName} · ${review.durationSeconds}s',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Color(0xFF6B6258)),
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
if (review.transcript?.trim().isNotEmpty == true) ...[
const SizedBox(height: 8),
@@ -12,11 +12,13 @@ import 'package:waveform_flutter/waveform_flutter.dart' show Amplitude;
import 'package:waveform_recorder/waveform_recorder.dart';
import '../../../app/router/app_router.dart';
import '../../../app/theme/mapflow_theme.dart';
import '../../../shared/auth/telegram_login_button.dart';
import '../../../shared/auth/telegram_session.dart' as telegram_session;
import '../application/place_cubit.dart';
import '../data/mapflow_api.dart';
import '../domain/place_models.dart';
import 'widgets/place_photo_card.dart';
const _mapboxAccessToken = String.fromEnvironment('MAPBOX_ACCESS_TOKEN');
const _mapboxStyle = String.fromEnvironment(
@@ -291,6 +293,9 @@ class _AdminReviewsButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final tokens = context.mapflowTokens;
final colorScheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.only(top: 8, right: 12),
child: FilledButton.icon(
@@ -298,9 +303,11 @@ class _AdminReviewsButton extends StatelessWidget {
icon: const Icon(Icons.table_rows_outlined, size: 18),
label: const Text('Отзывы'),
style: FilledButton.styleFrom(
backgroundColor: const Color(0xFFFFFBF5),
foregroundColor: const Color(0xFF17211D),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
backgroundColor: tokens.mapPanel,
foregroundColor: colorScheme.onSurface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(tokens.panelRadius),
),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
),
),
@@ -342,13 +349,15 @@ class _AvatarFallback extends StatelessWidget {
@override
Widget build(BuildContext context) {
final tokens = context.mapflowTokens;
return ColoredBox(
color: const Color(0xFFFFFBF5),
color: tokens.mapPanel,
child: Center(
child: Text(
text,
style: const TextStyle(
color: Color(0xFF17211D),
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w900,
),
),
@@ -456,6 +465,8 @@ class _MapError extends StatelessWidget {
@override
Widget build(BuildContext context) {
final tokens = context.mapflowTokens;
return Scaffold(
body: Stack(
children: [
@@ -472,7 +483,7 @@ class _MapError extends StatelessWidget {
child: Container(
margin: const EdgeInsets.all(12),
padding: const EdgeInsets.all(12),
color: const Color(0xFFFFFBF5),
color: tokens.mapPanel,
child: Text(
message,
maxLines: 3,
@@ -547,6 +558,7 @@ class _TraitBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
final controller = context.read<PlaceCubit>();
final tokens = context.mapflowTokens;
return SizedBox(
height: 54,
@@ -565,7 +577,7 @@ class _TraitBar extends StatelessWidget {
onSelected: (_) => selected
? controller.clearTrait()
: controller.selectTrait(item),
backgroundColor: const Color(0xFFFFFBF5),
backgroundColor: tokens.mapPanel,
selectedColor: Theme.of(context).colorScheme.primaryContainer,
side: BorderSide.none,
shape: const StadiumBorder(),
@@ -586,6 +598,7 @@ class _PlaceMarker extends StatelessWidget {
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.primary;
final tokens = context.mapflowTokens;
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
@@ -594,11 +607,11 @@ class _PlaceMarker extends StatelessWidget {
color: selected ? color : Colors.white,
shape: BoxShape.circle,
border: Border.all(color: color, width: selected ? 3 : 2),
boxShadow: const [
boxShadow: [
BoxShadow(
color: Color(0x33000000),
color: tokens.mapShadow,
blurRadius: 14,
offset: Offset(0, 8),
offset: const Offset(0, 8),
),
],
),
@@ -629,90 +642,13 @@ class _PlaceCarousel extends StatelessWidget {
separatorBuilder: (_, _) => const SizedBox(width: 10),
itemBuilder: (context, index) {
final place = places[index];
return _PlacePhotoCard(place: place, onTap: () => onSelect(place));
return PlacePhotoCard(place: place, onTap: () => onSelect(place));
},
),
);
}
}
class _PlacePhotoCard extends StatelessWidget {
const _PlacePhotoCard({required this.place, required this.onTap});
final PlaceRecommendation place;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
width: 150,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.white),
boxShadow: const [
BoxShadow(
color: Color(0x33000000),
blurRadius: 16,
offset: Offset(0, 8),
),
],
),
clipBehavior: Clip.antiAlias,
child: Stack(
fit: StackFit.expand,
children: [
if (place.photoUrls.isEmpty)
const ColoredBox(
color: Color(0xFF0F766E),
child: Icon(Icons.place_outlined, color: Colors.white),
)
else
PageView.builder(
itemCount: place.photoUrls.length,
itemBuilder: (context, index) {
return Image.network(
place.photoUrls[index],
fit: BoxFit.cover,
errorBuilder: (_, _, _) => Container(
color: const Color(0xFFE0D8CA),
child: const Icon(Icons.place_outlined),
),
);
},
),
const DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.transparent, Color(0xCC000000)],
),
),
),
Positioned(
left: 10,
right: 10,
bottom: 12,
child: Text(
place.name,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
height: 1.05,
),
),
),
],
),
),
);
}
}
class AddExperienceFlow extends StatefulWidget {
const AddExperienceFlow({
super.key,
@@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import '../../../../app/theme/mapflow_theme.dart';
import '../../domain/place_models.dart';
class PlacePhotoCard extends StatelessWidget {
const PlacePhotoCard({super.key, required this.place, required this.onTap});
final PlaceRecommendation place;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final tokens = context.mapflowTokens;
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: onTap,
child: Container(
width: 150,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(tokens.panelRadius),
border: Border.all(color: colorScheme.surface),
boxShadow: [
BoxShadow(
color: tokens.mapShadow,
blurRadius: 16,
offset: const Offset(0, 8),
),
],
),
clipBehavior: Clip.antiAlias,
child: Stack(
fit: StackFit.expand,
children: [
if (place.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),
),
);
},
),
const DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.transparent, Color(0xCC000000)],
),
),
),
Positioned(
left: 10,
right: 10,
bottom: 12,
child: Text(
place.name,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: tokens.onDark,
fontWeight: FontWeight.w900,
height: 1.05,
),
),
),
],
),
),
);
}
}