From 479dc25847d0cbc5ffb8a68db0fcd8713a01723f Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev <572431+veikab@users.noreply.github.com> Date: Fri, 12 Jun 2026 14:41:11 +0700 Subject: [PATCH] Move map visuals into theme tokens --- lib/app/theme/mapflow_theme.dart | 60 ++++++++++++ .../admin_voice_experiences_screen.dart | 20 ++-- .../mapflow/presentation/mapflow_shell.dart | 98 +++++++++++-------- .../widgets/place_photo_card.dart | 4 +- 4 files changed, 134 insertions(+), 48 deletions(-) diff --git a/lib/app/theme/mapflow_theme.dart b/lib/app/theme/mapflow_theme.dart index ad6a919..121f2cd 100644 --- a/lib/app/theme/mapflow_theme.dart +++ b/lib/app/theme/mapflow_theme.dart @@ -9,6 +9,16 @@ class MapflowThemeTokens extends ThemeExtension { required this.mapShadow, required this.danger, required this.onDark, + required this.darkSurface, + required this.darkSurfaceAlt, + required this.voiceAccent, + required this.voiceAccentSoft, + required this.onVoiceControl, + required this.snowflakeLine, + required this.snowflakeNode, + required this.snowflakeCenter, + required this.snowflakeLabel, + required this.imageScrim, required this.panelRadius, }); @@ -17,6 +27,16 @@ class MapflowThemeTokens extends ThemeExtension { final Color mapShadow; final Color danger; final Color onDark; + final Color darkSurface; + final Color darkSurfaceAlt; + final Color voiceAccent; + final Color voiceAccentSoft; + final Color onVoiceControl; + final Color snowflakeLine; + final Color snowflakeNode; + final Color snowflakeCenter; + final Color snowflakeLabel; + final Color imageScrim; final double panelRadius; @override @@ -26,6 +46,16 @@ class MapflowThemeTokens extends ThemeExtension { Color? mapShadow, Color? danger, Color? onDark, + Color? darkSurface, + Color? darkSurfaceAlt, + Color? voiceAccent, + Color? voiceAccentSoft, + Color? onVoiceControl, + Color? snowflakeLine, + Color? snowflakeNode, + Color? snowflakeCenter, + Color? snowflakeLabel, + Color? imageScrim, double? panelRadius, }) { return MapflowThemeTokens( @@ -34,6 +64,16 @@ class MapflowThemeTokens extends ThemeExtension { mapShadow: mapShadow ?? this.mapShadow, danger: danger ?? this.danger, onDark: onDark ?? this.onDark, + darkSurface: darkSurface ?? this.darkSurface, + darkSurfaceAlt: darkSurfaceAlt ?? this.darkSurfaceAlt, + voiceAccent: voiceAccent ?? this.voiceAccent, + voiceAccentSoft: voiceAccentSoft ?? this.voiceAccentSoft, + onVoiceControl: onVoiceControl ?? this.onVoiceControl, + snowflakeLine: snowflakeLine ?? this.snowflakeLine, + snowflakeNode: snowflakeNode ?? this.snowflakeNode, + snowflakeCenter: snowflakeCenter ?? this.snowflakeCenter, + snowflakeLabel: snowflakeLabel ?? this.snowflakeLabel, + imageScrim: imageScrim ?? this.imageScrim, panelRadius: panelRadius ?? this.panelRadius, ); } @@ -52,6 +92,16 @@ class MapflowThemeTokens extends ThemeExtension { mapShadow: Color.lerp(mapShadow, other.mapShadow, t)!, danger: Color.lerp(danger, other.danger, t)!, onDark: Color.lerp(onDark, other.onDark, t)!, + darkSurface: Color.lerp(darkSurface, other.darkSurface, t)!, + darkSurfaceAlt: Color.lerp(darkSurfaceAlt, other.darkSurfaceAlt, t)!, + voiceAccent: Color.lerp(voiceAccent, other.voiceAccent, t)!, + voiceAccentSoft: Color.lerp(voiceAccentSoft, other.voiceAccentSoft, t)!, + onVoiceControl: Color.lerp(onVoiceControl, other.onVoiceControl, t)!, + snowflakeLine: Color.lerp(snowflakeLine, other.snowflakeLine, t)!, + snowflakeNode: Color.lerp(snowflakeNode, other.snowflakeNode, t)!, + snowflakeCenter: Color.lerp(snowflakeCenter, other.snowflakeCenter, t)!, + snowflakeLabel: Color.lerp(snowflakeLabel, other.snowflakeLabel, t)!, + imageScrim: Color.lerp(imageScrim, other.imageScrim, t)!, panelRadius: lerpDouble(panelRadius, other.panelRadius, t), ); } @@ -66,6 +116,16 @@ class MapflowTheme { mapShadow: Color(0x33000000), danger: Color(0xFFE11D48), onDark: Colors.white, + darkSurface: Color(0xFF05030B), + darkSurfaceAlt: Color(0xFF15111D), + voiceAccent: Color(0xFFFF2D75), + voiceAccentSoft: Color(0xFFFF7A90), + onVoiceControl: Color(0xFF090613), + snowflakeLine: Color(0xFFE6DDD2), + snowflakeNode: Color(0xFFDED3C7), + snowflakeCenter: Color(0xFF241B18), + snowflakeLabel: Color(0xFF746A60), + imageScrim: Color(0xCC000000), panelRadius: 8, ); diff --git a/lib/features/mapflow/presentation/admin_voice_experiences_screen.dart b/lib/features/mapflow/presentation/admin_voice_experiences_screen.dart index c6e8e63..adcc6a4 100644 --- a/lib/features/mapflow/presentation/admin_voice_experiences_screen.dart +++ b/lib/features/mapflow/presentation/admin_voice_experiences_screen.dart @@ -165,6 +165,8 @@ class _AdminOntologySnowflake extends StatelessWidget { @override Widget build(BuildContext context) { + final tokens = context.mapflowTokens; + return SizedBox( height: 300, width: double.infinity, @@ -172,6 +174,7 @@ class _AdminOntologySnowflake extends StatelessWidget { painter: _AdminOntologySnowflakePainter( axes: _axes, selectedTags: selectedTags, + tokens: tokens, ), ), ); @@ -182,10 +185,12 @@ class _AdminOntologySnowflakePainter extends CustomPainter { const _AdminOntologySnowflakePainter({ required this.axes, required this.selectedTags, + required this.tokens, }); final List<_AdminOntologyAxis> axes; final Set selectedTags; + final MapflowThemeTokens tokens; @override void paint(Canvas canvas, Size size) { @@ -195,17 +200,17 @@ class _AdminOntologySnowflakePainter extends CustomPainter { final leafRadius = radius * 0.43; final baseLine = Paint() - ..color = const Color(0xFFE6DDD2) + ..color = tokens.snowflakeLine ..strokeWidth = 1.3 ..style = PaintingStyle.stroke; final selectedLine = Paint() - ..color = const Color(0xFFE11D48) + ..color = tokens.danger ..strokeWidth = 2.2 ..strokeCap = StrokeCap.round ..style = PaintingStyle.stroke; - final node = Paint()..color = const Color(0xFFDED3C7); - final selectedNode = Paint()..color = const Color(0xFFE11D48); - final centerNode = Paint()..color = const Color(0xFF241B18); + final node = Paint()..color = tokens.snowflakeNode; + final selectedNode = Paint()..color = tokens.danger; + final centerNode = Paint()..color = tokens.snowflakeCenter; canvas.drawCircle(center, 5, centerNode); _drawLabel(canvas, size, center + const Offset(0, 14), 'место', true); @@ -276,7 +281,7 @@ class _AdminOntologySnowflakePainter extends CustomPainter { text: TextSpan( text: label, style: TextStyle( - color: selected ? const Color(0xFFE11D48) : const Color(0xFF746A60), + color: selected ? tokens.danger : tokens.snowflakeLabel, fontSize: fontSize, fontWeight: selected ? FontWeight.w900 : FontWeight.w700, height: 1, @@ -298,7 +303,8 @@ class _AdminOntologySnowflakePainter extends CustomPainter { @override bool shouldRepaint(covariant _AdminOntologySnowflakePainter oldDelegate) { - return oldDelegate.selectedTags != selectedTags; + return oldDelegate.selectedTags != selectedTags || + oldDelegate.tokens != tokens; } } diff --git a/lib/features/mapflow/presentation/mapflow_shell.dart b/lib/features/mapflow/presentation/mapflow_shell.dart index e9d2cff..eb756f8 100644 --- a/lib/features/mapflow/presentation/mapflow_shell.dart +++ b/lib/features/mapflow/presentation/mapflow_shell.dart @@ -557,8 +557,8 @@ class _TraitBar extends StatelessWidget { @override Widget build(BuildContext context) { - final controller = context.read(); final tokens = context.mapflowTokens; + final controller = context.read(); return SizedBox( height: 54, @@ -801,6 +801,7 @@ class _AddExperienceFlowState extends State { @override Widget build(BuildContext context) { + final tokens = context.mapflowTokens; final controller = context.read(); final informationProgress = (_informationUnits / _minimumInformationUnits) .clamp(0.0, 1.0); @@ -854,7 +855,7 @@ class _AddExperienceFlowState extends State { }; return Scaffold( - backgroundColor: const Color(0xFF05030B), + backgroundColor: tokens.darkSurface, body: SafeArea( child: Padding( padding: EdgeInsets.fromLTRB( @@ -893,6 +894,8 @@ class _IntroStep extends StatelessWidget { @override Widget build(BuildContext context) { + final tokens = context.mapflowTokens; + return _StepLayout( body: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -902,17 +905,17 @@ class _IntroStep extends StatelessWidget { width: 70, height: 70, decoration: BoxDecoration( - color: const Color(0xFFE11D48), - borderRadius: BorderRadius.circular(8), + color: tokens.danger, + borderRadius: BorderRadius.circular(tokens.panelRadius), ), - child: const Icon(Icons.graphic_eq, color: Colors.white, size: 38), + 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: Colors.white, + color: tokens.onDark, fontWeight: FontWeight.w800, height: 1.18, letterSpacing: 0, @@ -940,6 +943,8 @@ class _PlaceStep extends StatelessWidget { @override Widget build(BuildContext context) { + final tokens = context.mapflowTokens; + return _StepLayout( body: Column( children: [ @@ -958,15 +963,15 @@ class _PlaceStep extends StatelessWidget { future: placesFuture, builder: (context, snapshot) { if (snapshot.connectionState != ConnectionState.done) { - return const Center( - child: CircularProgressIndicator(color: Color(0xFFFF2D75)), + return Center( + child: CircularProgressIndicator(color: tokens.voiceAccent), ); } if (snapshot.hasError) { - return const Center( + return Center( child: Icon( Icons.error_outline, - color: Colors.white, + color: tokens.onDark, size: 42, ), ); @@ -978,15 +983,15 @@ class _PlaceStep extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon( + Icon( Icons.location_off_outlined, - color: Colors.white, + color: tokens.onDark, size: 42, ), const SizedBox(height: 10), Text( 'Нет мест в $radiusMetersм', - style: const TextStyle(color: Colors.white), + style: TextStyle(color: tokens.onDark), ), ], ), @@ -1028,13 +1033,14 @@ class _NearbyPlaceCard extends StatelessWidget { @override Widget build(BuildContext context) { final primaryType = _formatType(place.googlePrimaryType); + final tokens = context.mapflowTokens; return Material( - color: const Color(0xFF15111D), - borderRadius: BorderRadius.circular(8), + color: tokens.darkSurfaceAlt, + borderRadius: BorderRadius.circular(tokens.panelRadius), child: InkWell( onTap: disabled ? null : onTap, - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(tokens.panelRadius), child: Padding( padding: const EdgeInsets.all(14), child: Column( @@ -1043,15 +1049,18 @@ class _NearbyPlaceCard extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Icon(Icons.place_outlined, color: Colors.white70), + Icon( + Icons.place_outlined, + color: tokens.onDark.withValues(alpha: 0.70), + ), const SizedBox(width: 10), Expanded( child: Text( place.name, maxLines: 2, overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: tokens.onDark, fontWeight: FontWeight.w900, height: 1.1, ), @@ -1090,19 +1099,23 @@ class _PlaceTypeChip extends StatelessWidget { @override Widget build(BuildContext context) { + final tokens = context.mapflowTokens; + return DecoratedBox( decoration: BoxDecoration( color: primary - ? const Color(0xFFFF2D75) - : Colors.white.withValues(alpha: 0.12), - borderRadius: BorderRadius.circular(8), + ? tokens.voiceAccent + : tokens.onDark.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(tokens.panelRadius), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5), child: Text( label, style: TextStyle( - color: primary ? Colors.white : const Color(0xFFECE7F0), + color: primary + ? tokens.onDark + : tokens.onDark.withValues(alpha: 0.92), fontSize: 12, fontWeight: FontWeight.w700, height: 1, @@ -1140,6 +1153,7 @@ class _VoiceStep extends StatelessWidget { @override Widget build(BuildContext context) { + final tokens = context.mapflowTokens; final canFinish = canContinue; return Column( @@ -1158,11 +1172,11 @@ class _VoiceStep extends StatelessWidget { ], Expanded(child: _VoiceProgressGrid(progress: informationProgress)), if (!micAllowed) - const Padding( - padding: EdgeInsets.only(bottom: 10), + Padding( + padding: const EdgeInsets.only(bottom: 10), child: Icon( Icons.mic_off_outlined, - color: Color(0xFFFF7A90), + color: tokens.voiceAccentSoft, size: 22, ), ), @@ -1249,6 +1263,8 @@ class _VoiceProgressCell extends StatelessWidget { @override Widget build(BuildContext context) { + final tokens = context.mapflowTokens; + return AnimatedContainer( duration: const Duration(milliseconds: 180), curve: Curves.easeOutCubic, @@ -1256,13 +1272,13 @@ class _VoiceProgressCell extends StatelessWidget { height: size, decoration: BoxDecoration( color: filled - ? const Color(0xFFFF2D75) - : Colors.white.withValues(alpha: 0.11), + ? tokens.voiceAccent + : tokens.onDark.withValues(alpha: 0.11), borderRadius: BorderRadius.circular(3), boxShadow: filled ? [ BoxShadow( - color: const Color(0xFFFF2D75).withValues(alpha: 0.34), + color: tokens.voiceAccent.withValues(alpha: 0.34), blurRadius: 12, spreadRadius: 1, ), @@ -1332,6 +1348,8 @@ class _VoiceRecordButtonState extends State<_VoiceRecordButton> @override Widget build(BuildContext context) { + final tokens = context.mapflowTokens; + return SizedBox( width: 280, height: 280, @@ -1355,7 +1373,7 @@ class _VoiceRecordButtonState extends State<_VoiceRecordButton> decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( - color: const Color(0xFFFF2D75).withValues( + color: tokens.voiceAccent.withValues( alpha: widget.isRecording ? ((0.06 + level * 0.26) * (1 - ((pulse + offset) % 1))) @@ -1373,8 +1391,8 @@ class _VoiceRecordButtonState extends State<_VoiceRecordButton> value: widget.progress, strokeWidth: 8, strokeCap: StrokeCap.round, - color: const Color(0xFFFF2D75), - backgroundColor: Colors.white.withValues(alpha: 0.12), + color: tokens.voiceAccent, + backgroundColor: tokens.onDark.withValues(alpha: 0.12), ), ), child!, @@ -1387,10 +1405,10 @@ class _VoiceRecordButtonState extends State<_VoiceRecordButton> child: FilledButton( onPressed: widget.enabled ? widget.onPressed : null, style: FilledButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: const Color(0xFF090613), - disabledBackgroundColor: Colors.white.withValues(alpha: 0.28), - disabledForegroundColor: Colors.white.withValues(alpha: 0.52), + backgroundColor: tokens.onDark, + foregroundColor: tokens.onVoiceControl, + disabledBackgroundColor: tokens.onDark.withValues(alpha: 0.28), + disabledForegroundColor: tokens.onDark.withValues(alpha: 0.52), shape: const CircleBorder(), padding: EdgeInsets.zero, elevation: 0, @@ -1442,6 +1460,8 @@ class _StoryProgress extends StatelessWidget { @override Widget build(BuildContext context) { + final tokens = context.mapflowTokens; + return Row( children: [ Expanded( @@ -1455,11 +1475,11 @@ class _StoryProgress extends StatelessWidget { decoration: BoxDecoration( color: index <= step ? (dark - ? const Color(0xFFFF2D75) + ? tokens.voiceAccent : Theme.of(context).colorScheme.primary) : (dark - ? Colors.white.withValues(alpha: 0.16) - : const Color(0xFFE0D8CA)), + ? tokens.onDark.withValues(alpha: 0.16) + : tokens.mapPanelBorder), borderRadius: BorderRadius.circular(99), ), ), diff --git a/lib/features/mapflow/presentation/widgets/place_photo_card.dart b/lib/features/mapflow/presentation/widgets/place_photo_card.dart index 63002ed..c186a58 100644 --- a/lib/features/mapflow/presentation/widgets/place_photo_card.dart +++ b/lib/features/mapflow/presentation/widgets/place_photo_card.dart @@ -52,12 +52,12 @@ class PlacePhotoCard extends StatelessWidget { ); }, ), - const DecoratedBox( + DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [Colors.transparent, Color(0xCC000000)], + colors: [Colors.transparent, tokens.imageScrim], ), ), ),