From 1820739a49bb3f2d48603bebe64c378562555387 Mon Sep 17 00:00:00 2001 From: Developer Date: Fri, 16 Oct 2026 23:18:24 -0400 Subject: [PATCH] fix(social): standardize community feed typography and theme colors (#49) --- .../social/feed/social_feed_screen.dart | 444 ++++++++++-------- .../widgets/feed_comments_bottom_sheet.dart | 97 ++-- .../feed/widgets/share_session_modal.dart | 4 +- test/community_social_feed_test.dart | 133 ++++++ 4 files changed, 429 insertions(+), 249 deletions(-) diff --git a/lib/features/social/feed/social_feed_screen.dart b/lib/features/social/feed/social_feed_screen.dart index 16b3e27..31f5159 100644 --- a/lib/features/social/feed/social_feed_screen.dart +++ b/lib/features/social/feed/social_feed_screen.dart @@ -120,20 +120,26 @@ class _SocialFeedScreenState extends ConsumerState { ), ); + final isLight = Theme.of(context).brightness == Brightness.light; + final secondaryTextColor = + isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; + final accentColor = + isLight ? AppColors.brandBlueStrong : colors.brandCyanBright; + if (!widget.showAppBar) { return Container( - color: Theme.of(context).colorScheme.surface, + color: colors.surfaceCanvas, child: content, ); } return Scaffold( - backgroundColor: Theme.of(context).colorScheme.surface, + backgroundColor: colors.surfaceCanvas, appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.surface, + backgroundColor: colors.surfaceCanvas, elevation: 0, leading: IconButton( - icon: const Icon(Icons.arrow_back_rounded, color: Colors.white), + icon: Icon(Icons.arrow_back_rounded, color: colors.textPrimary), onPressed: () { if (context.canPop()) { context.pop(); @@ -147,12 +153,12 @@ class _SocialFeedScreenState extends ConsumerState { Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( - color: AppColors.electricCyan.withValues(alpha: 0.15), + color: accentColor.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(8), ), - child: const Icon( + child: Icon( Icons.dynamic_feed_rounded, - color: AppColors.electricCyan, + color: accentColor, size: 20, ), ), @@ -162,7 +168,7 @@ class _SocialFeedScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - const Text( + Text( 'Community Feed', overflow: TextOverflow.ellipsis, style: TextStyle( @@ -170,7 +176,7 @@ class _SocialFeedScreenState extends ConsumerState { fontSize: 16, fontWeight: FontWeight.w700, letterSpacing: 0.2, - color: Colors.white, + color: colors.textPrimary, ), ), Text( @@ -181,7 +187,7 @@ class _SocialFeedScreenState extends ConsumerState { fontSize: 9, fontWeight: FontWeight.w700, letterSpacing: 0.8, - color: const Color(0xFF94A3B8), + color: secondaryTextColor, ), ), ], @@ -193,9 +199,7 @@ class _SocialFeedScreenState extends ConsumerState { IconButton( icon: Icon( _isSearchExpanded ? Icons.search_off_rounded : Icons.search_rounded, - color: _isSearchExpanded - ? AppColors.electricCyan - : const Color(0xFF94A3B8), + color: _isSearchExpanded ? accentColor : secondaryTextColor, ), tooltip: 'Search Routes', onPressed: () { @@ -209,7 +213,7 @@ class _SocialFeedScreenState extends ConsumerState { }, ), IconButton( - icon: const Icon(Icons.refresh_rounded, color: Color(0xFF94A3B8)), + icon: Icon(Icons.refresh_rounded, color: secondaryTextColor), tooltip: 'Refresh Feed', onPressed: () { ref.invalidate(socialFeedNotifierProvider); @@ -230,9 +234,15 @@ class _SocialFeedScreenState extends ConsumerState { String searchQuery, MotorSignalThemeColors colors, ) { + final isLight = Theme.of(context).brightness == Brightness.light; + final secondaryTextColor = + isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; + final accentColor = + isLight ? AppColors.brandBlueStrong : colors.brandCyanBright; + return Container( padding: const EdgeInsets.fromLTRB(16, 10, 16, 8), - color: Theme.of(context).colorScheme.surface, + color: colors.surfaceCanvas, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -245,14 +255,10 @@ class _SocialFeedScreenState extends ConsumerState { Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( - color: Theme.of(context).brightness == Brightness.light - ? const Color(0xFFE0F7FA) - : const Color(0xFF161B22), + color: colors.surfaceCardSecondary, borderRadius: BorderRadius.circular(20), border: Border.all( - color: AppColors.electricCyan.withValues( - alpha: Theme.of(context).brightness == Brightness.light ? 0.6 : 0.25, - ), + color: accentColor.withValues(alpha: isLight ? 0.5 : 0.3), ), ), child: Row( @@ -261,16 +267,9 @@ class _SocialFeedScreenState extends ConsumerState { Container( width: 8, height: 8, - decoration: const BoxDecoration( - color: AppColors.electricCyan, + decoration: BoxDecoration( + color: accentColor, shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: AppColors.electricCyan, - blurRadius: 6, - spreadRadius: 1, - ), - ], ), ), const SizedBox(width: 8), @@ -281,9 +280,7 @@ class _SocialFeedScreenState extends ConsumerState { fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 0.6, - color: Theme.of(context).brightness == Brightness.light - ? const Color(0xFF006064) - : const Color(0xFF9CF0FF), + color: accentColor, ), ), ], @@ -295,7 +292,7 @@ class _SocialFeedScreenState extends ConsumerState { key: const ValueKey('feed_search_toggle_button'), icon: Icon( _isSearchExpanded ? Icons.close_rounded : Icons.search_rounded, - color: AppColors.electricCyan, + color: accentColor, size: 20, ), tooltip: 'Search Feed', @@ -318,36 +315,36 @@ class _SocialFeedScreenState extends ConsumerState { Container( height: 42, decoration: BoxDecoration( - color: const Color(0xFF161B22), + color: colors.surfaceCardPrimary, borderRadius: BorderRadius.circular(10), border: Border.all( - color: AppColors.electricCyan.withValues(alpha: 0.3), + color: accentColor.withValues(alpha: 0.4), ), ), child: TextField( controller: _searchController, autofocus: true, - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 13, - color: Colors.white, + color: colors.textPrimary, ), decoration: InputDecoration( hintText: 'Search rider, route, bike, or region...', - hintStyle: const TextStyle( + hintStyle: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 13, - color: Color(0xFF64748B), + color: secondaryTextColor, ), - prefixIcon: const Icon( + prefixIcon: Icon( Icons.search_rounded, size: 18, - color: Color(0xFF94A3B8), + color: secondaryTextColor, ), suffixIcon: _searchController.text.isNotEmpty ? IconButton( icon: const Icon(Icons.close_rounded, size: 16), - color: const Color(0xFF94A3B8), + color: secondaryTextColor, onPressed: () { _searchController.clear(); ref.read(feedSearchQueryProvider.notifier).state = ''; @@ -406,7 +403,7 @@ class _SocialFeedScreenState extends ConsumerState { Container( width: 1, height: 20, - color: colors.outlineVariant.withValues(alpha: 0.4), + color: colors.surfaceBorder, ), const SizedBox(width: 8), _buildDisciplineChip( @@ -453,18 +450,18 @@ class _SocialFeedScreenState extends ConsumerState { const SizedBox(height: 6), Row( children: [ - const Icon( + Icon( Icons.filter_alt_outlined, size: 13, - color: AppColors.electricCyan, + color: accentColor, ), const SizedBox(width: 4), Text( 'Filtered by: ${activeDiscipline != RidingDiscipline.all ? activeDiscipline.displayName : ''}${activeDiscipline != RidingDiscipline.all && searchQuery.isNotEmpty ? ', ' : ''}${searchQuery.isNotEmpty ? '"$searchQuery"' : ''}', - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 11, - color: Color(0xFF94A3B8), + color: secondaryTextColor, ), ), const Spacer(), @@ -478,13 +475,13 @@ class _SocialFeedScreenState extends ConsumerState { _isSearchExpanded = false; }); }, - child: const Text( + child: Text( 'Reset', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 11, fontWeight: FontWeight.w700, - color: AppColors.electricCyan, + color: accentColor, ), ), ), @@ -501,6 +498,13 @@ class _SocialFeedScreenState extends ConsumerState { required String title, required bool isSelected, }) { + final colors = context.motorColors; + final isLight = Theme.of(context).brightness == Brightness.light; + final selectedBg = isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; + final selectedFg = isLight ? AppColors.onBrand : const Color(0xFF00272E); + final unselectedFg = + isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; + return InkWell( borderRadius: BorderRadius.circular(10), onTap: () { @@ -510,33 +514,23 @@ class _SocialFeedScreenState extends ConsumerState { duration: const Duration(milliseconds: 180), curve: Curves.easeInOut, height: 36, - padding: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.symmetric(horizontal: 10), alignment: Alignment.center, decoration: BoxDecoration( - color: isSelected ? AppColors.electricCyan : Colors.transparent, + color: isSelected ? selectedBg : colors.surfaceCardSecondary, borderRadius: BorderRadius.circular(10), - boxShadow: isSelected - ? [ - BoxShadow( - color: AppColors.electricCyan.withValues(alpha: 0.35), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ] - : null, + border: Border.all( + color: isSelected ? selectedBg : colors.surfaceBorder, + ), ), child: Text( title, style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 11.5, - fontWeight: FontWeight.w700, + fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600, letterSpacing: 0.1, - color: isSelected - ? const Color(0xFF00363D) - : (Theme.of(context).brightness == Brightness.light - ? const Color(0xFF334155) - : const Color(0xFFCBD5E1)), + color: isSelected ? selectedFg : unselectedFg, ), ), ), @@ -548,8 +542,12 @@ class _SocialFeedScreenState extends ConsumerState { required String label, required bool isSelected, }) { - final chipColor = _getDisciplineColor(discipline); + final colors = context.motorColors; final isLight = Theme.of(context).brightness == Brightness.light; + final selectedBg = isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; + final selectedFg = isLight ? AppColors.onBrand : const Color(0xFF00272E); + final unselectedFg = + isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; return InkWell( borderRadius: BorderRadius.circular(8), @@ -558,16 +556,14 @@ class _SocialFeedScreenState extends ConsumerState { }, child: AnimatedContainer( duration: const Duration(milliseconds: 160), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + height: 36, + padding: const EdgeInsets.symmetric(horizontal: 10), + alignment: Alignment.center, decoration: BoxDecoration( - color: isSelected - ? chipColor.withValues(alpha: 0.22) - : (isLight ? const Color(0xFFF1F5F9) : const Color(0xFF161B22)), + color: isSelected ? selectedBg : colors.surfaceCardSecondary, borderRadius: BorderRadius.circular(8), border: Border.all( - color: isSelected - ? chipColor.withValues(alpha: 0.7) - : (isLight ? const Color(0xFFCBD5E1) : const Color(0xFF263238)), + color: isSelected ? selectedBg : colors.surfaceBorder, width: isSelected ? 1.2 : 1.0, ), ), @@ -576,10 +572,8 @@ class _SocialFeedScreenState extends ConsumerState { style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 11.5, - fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500, - color: isSelected - ? (isLight ? const Color(0xFF0F172A) : Colors.white) - : (isLight ? const Color(0xFF475569) : const Color(0xFF94A3B8)), + fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600, + color: isSelected ? selectedFg : unselectedFg, ), ), ), @@ -595,6 +589,11 @@ class _SocialFeedScreenState extends ConsumerState { ) { final hasCustomFilters = activeDiscipline != RidingDiscipline.all || searchQuery.isNotEmpty; + final isLight = Theme.of(context).brightness == Brightness.light; + final secondaryTextColor = + isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; + final accentColor = + isLight ? AppColors.brandBlueStrong : colors.brandCyanBright; return Center( child: Padding( @@ -605,7 +604,7 @@ class _SocialFeedScreenState extends ConsumerState { Icon( Icons.dynamic_feed_outlined, size: 56, - color: const Color(0xFF64748B), + color: secondaryTextColor, ), const SizedBox(height: 16), Text( @@ -616,7 +615,7 @@ class _SocialFeedScreenState extends ConsumerState { fontFamily: AppTypography.fontHeadline, fontSize: 16, fontWeight: FontWeight.w700, - color: Theme.of(context).colorScheme.onSurface, + color: colors.textPrimary, ), ), const SizedBox(height: 6), @@ -628,7 +627,7 @@ class _SocialFeedScreenState extends ConsumerState { style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 13, - color: Theme.of(context).colorScheme.onSurfaceVariant, + color: secondaryTextColor, ), ), const SizedBox(height: 16), @@ -642,9 +641,9 @@ class _SocialFeedScreenState extends ConsumerState { icon: const Icon(Icons.two_wheeler_rounded, size: 16), label: const Text('Group Rides'), style: OutlinedButton.styleFrom( - foregroundColor: AppColors.electricCyan, + foregroundColor: accentColor, side: BorderSide( - color: AppColors.electricCyan.withValues(alpha: 0.5), + color: accentColor.withValues(alpha: 0.5), ), ), onPressed: () => context.push('/group-rides'), @@ -654,9 +653,9 @@ class _SocialFeedScreenState extends ConsumerState { icon: const Icon(Icons.groups_rounded, size: 16), label: const Text('Clubs'), style: OutlinedButton.styleFrom( - foregroundColor: AppColors.electricCyan, + foregroundColor: accentColor, side: BorderSide( - color: AppColors.electricCyan.withValues(alpha: 0.5), + color: accentColor.withValues(alpha: 0.5), ), ), onPressed: () => context.push('/social/clubs'), @@ -666,9 +665,9 @@ class _SocialFeedScreenState extends ConsumerState { icon: const Icon(Icons.person_add_rounded, size: 16), label: const Text('Friends'), style: OutlinedButton.styleFrom( - foregroundColor: AppColors.electricCyan, + foregroundColor: accentColor, side: BorderSide( - color: AppColors.electricCyan.withValues(alpha: 0.5), + color: accentColor.withValues(alpha: 0.5), ), ), onPressed: () => context.push('/social/friends'), @@ -681,9 +680,9 @@ class _SocialFeedScreenState extends ConsumerState { icon: const Icon(Icons.refresh_rounded, size: 16), label: const Text('Reset All Filters'), style: OutlinedButton.styleFrom( - foregroundColor: AppColors.electricCyan, + foregroundColor: accentColor, side: BorderSide( - color: AppColors.electricCyan.withValues(alpha: 0.5), + color: accentColor.withValues(alpha: 0.5), ), ), onPressed: () { @@ -704,16 +703,18 @@ class _SocialFeedScreenState extends ConsumerState { } Widget _buildStickyShareButton(BuildContext context) { + final isLight = Theme.of(context).brightness == Brightness.light; + final buttonBg = isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; + final buttonFg = isLight ? AppColors.onBrand : const Color(0xFF00272E); + return Container( height: 52, decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), - gradient: const LinearGradient( - colors: [Color(0xFF00E5FF), Color(0xFF00B0FF)], - ), + color: buttonBg, boxShadow: [ BoxShadow( - color: const Color(0xFF00E5FF).withValues(alpha: 0.35), + color: buttonBg.withValues(alpha: 0.3), blurRadius: 16, offset: const Offset(0, 4), ), @@ -724,7 +725,7 @@ class _SocialFeedScreenState extends ConsumerState { style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, - foregroundColor: const Color(0xFF00272E), + foregroundColor: buttonFg, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), @@ -755,18 +756,34 @@ class _SocialFeedScreenState extends ConsumerState { } } -Color _getDisciplineColor(RidingDiscipline discipline) { +Color _getDisciplineColor(RidingDiscipline discipline, {bool isLight = false}) { + if (isLight) { + switch (discipline) { + case RidingDiscipline.canyonSport: + return AppColors.semanticWarningLight; + case RidingDiscipline.advDualSport: + return AppColors.semanticSuccessLight; + case RidingDiscipline.touring: + return AppColors.brandBlueStrong; + case RidingDiscipline.trackDay: + return AppColors.semanticDanger; + case RidingDiscipline.cruiser: + return AppColors.brandBlueStrong; + case RidingDiscipline.all: + return AppColors.brandBlueStrong; + } + } switch (discipline) { case RidingDiscipline.canyonSport: - return const Color(0xFFF59E0B); + return AppColors.hazardAmber; case RidingDiscipline.advDualSport: - return const Color(0xFF10B981); + return AppColors.telemetryGreen; case RidingDiscipline.touring: - return const Color(0xFF38BDF8); + return AppColors.electricCyan; case RidingDiscipline.trackDay: - return const Color(0xFFF43F5E); + return AppColors.semanticDangerDark; case RidingDiscipline.cruiser: - return const Color(0xFFA855F7); + return AppColors.electricCyan; case RidingDiscipline.all: return AppColors.electricCyan; } @@ -863,6 +880,19 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> @override Widget build(BuildContext context) { + final colors = context.motorColors; + final isLight = Theme.of(context).brightness == Brightness.light; + final secondaryTextColor = + isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; + final accentColor = + isLight ? AppColors.brandBlueStrong : colors.brandCyanBright; + final demoColor = + isLight ? AppColors.semanticWarningLight : AppColors.hazardAmber; + final disciplineColor = + _getDisciplineColor(widget.item.discipline, isLight: isLight); + final bumpedBg = isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; + final bumpedFg = isLight ? AppColors.onBrand : const Color(0xFF00272E); + final item = widget.item; final user = ref.watch(currentUserProvider); final isBumped = @@ -871,14 +901,14 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> return Container( decoration: BoxDecoration( - color: const Color(0xFF161B22), + color: colors.surfaceCardPrimary, borderRadius: BorderRadius.circular(18), - border: Border.all(color: const Color(0xFF263238)), - boxShadow: const [ + border: Border.all(color: colors.surfaceBorder), + boxShadow: [ BoxShadow( - color: Colors.black45, + color: colors.glowShadow, blurRadius: 14, - offset: Offset(0, 6), + offset: const Offset(0, 6), ), ], ), @@ -896,10 +926,10 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> children: [ CircleAvatar( radius: 22, - backgroundColor: const Color(0xFF222936), - child: const Icon( + backgroundColor: colors.surfaceCardSecondary, + child: Icon( Icons.sports_motorsports_rounded, - color: AppColors.electricCyan, + color: accentColor, size: 26, ), ), @@ -910,16 +940,12 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> width: 11, height: 11, decoration: BoxDecoration( - color: AppColors.electricCyan, + color: accentColor, shape: BoxShape.circle, border: Border.all( - color: const Color(0xFF161B22), width: 1.5), - boxShadow: const [ - BoxShadow( - color: AppColors.electricCyan, - blurRadius: 4, - ), - ], + color: colors.surfaceCardPrimary, + width: 1.5, + ), ), ), ), @@ -939,11 +965,11 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> children: [ Text( '@${item.authorHandle}', - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 15, fontWeight: FontWeight.w700, - color: Color(0xFFF1F5F9), + color: colors.textPrimary, ), ), if (item.isDemo) ...[ @@ -952,20 +978,20 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), decoration: BoxDecoration( - color: const Color(0xFFF59E0B).withValues(alpha: 0.2), + color: demoColor.withValues(alpha: 0.14), borderRadius: BorderRadius.circular(4), border: Border.all( - color: const Color(0xFFF59E0B).withValues(alpha: 0.4), + color: demoColor.withValues(alpha: 0.4), ), ), - child: const Text( + child: Text( 'DEMO SAMPLE', style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 8.5, fontWeight: FontWeight.w800, letterSpacing: 0.5, - color: Color(0xFFF59E0B), + color: demoColor, ), ), ), @@ -975,18 +1001,17 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), decoration: BoxDecoration( - color: AppColors.electricCyan - .withValues(alpha: 0.18), + color: accentColor.withValues(alpha: 0.14), borderRadius: BorderRadius.circular(4), ), child: Text( item.authorBadge!, - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 9, fontWeight: FontWeight.w800, letterSpacing: 0.6, - color: AppColors.electricCyan, + color: accentColor, ), ), ), @@ -996,12 +1021,10 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), decoration: BoxDecoration( - color: _getDisciplineColor(item.discipline) - .withValues(alpha: 0.18), + color: disciplineColor.withValues(alpha: 0.14), borderRadius: BorderRadius.circular(4), border: Border.all( - color: _getDisciplineColor(item.discipline) - .withValues(alpha: 0.35), + color: disciplineColor.withValues(alpha: 0.35), width: 0.8, ), ), @@ -1012,7 +1035,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> fontSize: 8.5, fontWeight: FontWeight.w800, letterSpacing: 0.5, - color: _getDisciplineColor(item.discipline), + color: disciplineColor, ), ), ), @@ -1024,21 +1047,21 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> Text( item.clubName!, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 12, - color: Color(0xFF94A3B8), + color: secondaryTextColor, ), ), ], const SizedBox(height: 2), Text( '${_formatRelativeTime(item.timestamp)} • ${item.locationName}', - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 12.5, fontWeight: FontWeight.w500, - color: Color(0xFFCBD5E1), + color: secondaryTextColor, ), ), ], @@ -1047,12 +1070,15 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> // Overflow options menu PopupMenuButton( - icon: const Icon(Icons.more_vert, - color: Color(0xFF849396), size: 20), - color: const Color(0xFF181C21), + icon: Icon( + Icons.more_vert, + color: secondaryTextColor, + size: 20, + ), + color: colors.surfaceCardSecondary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), - side: const BorderSide(color: Color(0xFF272A30)), + side: BorderSide(color: colors.surfaceBorder), ), onSelected: (val) { ScaffoldMessenger.of(context).showSnackBar( @@ -1063,21 +1089,26 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> ); }, itemBuilder: (ctx) => [ - const PopupMenuItem( + PopupMenuItem( value: 'copy_link', - child: Text('Copy Route Link', - style: TextStyle(color: Colors.white, fontSize: 13)), + child: Text( + 'Copy Route Link', + style: TextStyle(color: colors.textPrimary, fontSize: 13), + ), ), - const PopupMenuItem( + PopupMenuItem( value: 'export_gpx', - child: Text('Export GPX File', - style: TextStyle(color: Colors.white, fontSize: 13)), + child: Text( + 'Export GPX File', + style: TextStyle(color: colors.textPrimary, fontSize: 13), + ), ), - const PopupMenuItem( + PopupMenuItem( value: 'report', - child: Text('Report Content', - style: - TextStyle(color: Colors.redAccent, fontSize: 13)), + child: Text( + 'Report Content', + style: TextStyle(color: colors.dangerRed, fontSize: 13), + ), ), ], ), @@ -1093,12 +1124,12 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> children: [ Text( item.rideTitle, - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 18, fontWeight: FontWeight.w700, letterSpacing: -0.3, - color: Colors.white, + color: colors.textPrimary, ), ), const SizedBox(height: 6), @@ -1106,17 +1137,17 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: const Color(0xFF12161D), + color: colors.surfaceCardSecondary, borderRadius: BorderRadius.circular(6), - border: Border.all(color: const Color(0xFF263238)), + border: Border.all(color: colors.surfaceBorder), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon( + Icon( Icons.sports_motorsports_rounded, size: 14, - color: AppColors.electricCyan, + color: accentColor, ), const SizedBox(width: 6), Flexible( @@ -1124,11 +1155,11 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> item.bikeModel, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 11.5, fontWeight: FontWeight.w600, - color: Color(0xFFE2E8F0), + color: colors.textPrimary, ), ), ), @@ -1137,18 +1168,18 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> Container( width: 3, height: 3, - decoration: const BoxDecoration( - color: Color(0xFF64748B), + decoration: BoxDecoration( + color: secondaryTextColor, shape: BoxShape.circle, ), ), const SizedBox(width: 6), Text( item.regionName!, - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 11, - color: Color(0xFF94A3B8), + color: secondaryTextColor, ), ), ], @@ -1166,26 +1197,28 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: const Color(0xFF0F1318), + color: colors.surfaceCardSecondary, borderRadius: BorderRadius.circular(14), - border: Border.all(color: const Color(0xFF1E2633)), + border: Border.all(color: colors.surfaceBorder), ), child: Column( children: [ Row( children: [ _buildMetricCell( + context: context, label: 'DISTANCE', value: item.distanceMiles.toStringAsFixed(1), unit: 'MI', - valueColor: Colors.white, + valueColor: colors.textPrimary, ), const SizedBox(width: 6), _buildMetricCell( + context: context, label: 'DURATION', value: _formatDuration(item.durationSeconds), unit: '', - valueColor: Colors.white, + valueColor: colors.textPrimary, ), ], ), @@ -1193,20 +1226,22 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> Row( children: [ _buildMetricCell( + context: context, label: 'MAX LEAN', value: '${item.maxLeanAngleDeg.toStringAsFixed(0)}°', unit: item.maxLeanSide, - valueColor: AppColors.electricCyan, - indicatorColor: AppColors.electricCyan, + valueColor: accentColor, + indicatorColor: accentColor, ), const SizedBox(width: 6), _buildMetricCell( + context: context, label: 'PACE', value: item.paceFactor.split(' ').first, unit: item.paceFactor.split(' ').length > 1 ? item.paceFactor.split(' ').sublist(1).join(' ') : '', - valueColor: const Color(0xFFB4C5FF), + valueColor: accentColor, ), ], ), @@ -1246,19 +1281,14 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> height: 48, decoration: BoxDecoration( color: isBumped - ? AppColors.electricCyan - : const Color(0xFF272A30), + ? bumpedBg + : colors.surfaceCardSecondary, borderRadius: BorderRadius.circular(12), - boxShadow: isBumped - ? [ - BoxShadow( - color: AppColors.electricCyan - .withValues(alpha: 0.35), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ] - : null, + border: Border.all( + color: isBumped + ? bumpedBg + : colors.surfaceBorder, + ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -1274,8 +1304,8 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> fontSize: 13, fontWeight: FontWeight.w700, color: isBumped - ? const Color(0xFF00363D) - : Colors.white, + ? bumpedFg + : colors.textPrimary, ), ), ), @@ -1297,25 +1327,26 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> child: Container( height: 48, decoration: BoxDecoration( - color: const Color(0xFF272A30), + color: colors.surfaceCardSecondary, borderRadius: BorderRadius.circular(12), + border: Border.all(color: colors.surfaceBorder), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon( + Icon( Icons.chat_bubble_outline_rounded, size: 18, - color: AppColors.electricCyan, + color: accentColor, ), const SizedBox(width: 6), Text( '${item.comments.length}', - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 13, fontWeight: FontWeight.w700, - color: Colors.white, + color: colors.textPrimary, ), ), ], @@ -1350,13 +1381,14 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> width: 48, height: 48, decoration: BoxDecoration( - color: const Color(0xFF272A30), + color: colors.surfaceCardSecondary, borderRadius: BorderRadius.circular(12), + border: Border.all(color: colors.surfaceBorder), ), - child: const Icon( + child: Icon( Icons.download_rounded, size: 20, - color: Colors.white, + color: colors.textPrimary, ), ), ), @@ -1375,21 +1407,21 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> height: 44, width: double.infinity, decoration: BoxDecoration( - color: const Color(0xFF181C21), + color: colors.surfaceCardSecondary, borderRadius: BorderRadius.circular(12), border: Border.all( - color: AppColors.electricCyan.withValues(alpha: 0.25), + color: accentColor.withValues(alpha: 0.35), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, - children: const [ + children: [ Icon( Icons.bolt_rounded, size: 18, - color: AppColors.electricCyan, + color: accentColor, ), - SizedBox(width: 8), + const SizedBox(width: 8), Flexible( child: Text( 'INSPECT TELEMETRY GRAPH', @@ -1399,7 +1431,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 0.8, - color: AppColors.electricCyan, + color: accentColor, ), ), ), @@ -1416,19 +1448,25 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> } Widget _buildMetricCell({ + required BuildContext context, required String label, required String value, required String unit, required Color valueColor, Color? indicatorColor, }) { + final colors = context.motorColors; + final isLight = Theme.of(context).brightness == Brightness.light; + final secondaryTextColor = + isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; + return Expanded( child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), decoration: BoxDecoration( - color: const Color(0xFF161B22), + color: colors.surfaceCardPrimary, borderRadius: BorderRadius.circular(10), - border: Border.all(color: const Color(0xFF222936), width: 0.8), + border: Border.all(color: colors.surfaceBorder, width: 0.8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1440,12 +1478,12 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> label, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 10.5, fontWeight: FontWeight.w700, letterSpacing: 0.6, - color: Color(0xFF94A3B8), + color: secondaryTextColor, ), ), ), @@ -1485,11 +1523,11 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> unit, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 10, fontWeight: FontWeight.w700, - color: Color(0xFFCBD5E1), + color: secondaryTextColor, ), ), ], diff --git a/lib/features/social/feed/widgets/feed_comments_bottom_sheet.dart b/lib/features/social/feed/widgets/feed_comments_bottom_sheet.dart index 55d8aee..c15a0a8 100644 --- a/lib/features/social/feed/widgets/feed_comments_bottom_sheet.dart +++ b/lib/features/social/feed/widgets/feed_comments_bottom_sheet.dart @@ -100,6 +100,14 @@ class _FeedCommentsBottomSheetState @override Widget build(BuildContext context) { final colors = context.motorColors; + final isLight = Theme.of(context).brightness == Brightness.light; + final secondaryTextColor = + isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; + final accentColor = + isLight ? AppColors.brandBlueStrong : colors.brandCyanBright; + final sendBg = isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; + final sendFg = isLight ? AppColors.onBrand : const Color(0xFF00363D); + final bottomInset = MediaQuery.of(context).viewInsets.bottom; final feedItems = ref.watch(socialFeedNotifierProvider); final livePost = feedItems.cast().firstWhere( @@ -116,14 +124,14 @@ class _FeedCommentsBottomSheetState ), child: Container( decoration: BoxDecoration( - color: const Color(0xFF101419), + color: colors.surfaceCardPrimary, borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), - border: Border.all(color: colors.outlineVariant.withValues(alpha: 0.5)), - boxShadow: const [ + border: Border.all(color: colors.surfaceBorder), + boxShadow: [ BoxShadow( - color: Colors.black87, + color: colors.glowShadow, blurRadius: 30, - offset: Offset(0, -10), + offset: const Offset(0, -10), ), ], ), @@ -139,7 +147,7 @@ class _FeedCommentsBottomSheetState width: 44, height: 4, decoration: BoxDecoration( - color: colors.outlineVariant, + color: colors.surfaceBorder, borderRadius: BorderRadius.circular(2), ), ), @@ -149,9 +157,9 @@ class _FeedCommentsBottomSheetState padding: const EdgeInsets.fromLTRB(20, 8, 12, 12), child: Row( children: [ - const Icon( + Icon( Icons.chat_bubble_outline_rounded, - color: AppColors.electricCyan, + color: accentColor, size: 20, ), const SizedBox(width: 8), @@ -161,7 +169,7 @@ class _FeedCommentsBottomSheetState fontFamily: AppTypography.fontHeadline, fontSize: 16, fontWeight: FontWeight.w700, - color: Colors.white, + color: colors.textPrimary, ), ), const SizedBox(width: 8), @@ -169,33 +177,35 @@ class _FeedCommentsBottomSheetState padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2), decoration: BoxDecoration( - color: const Color(0xFF1D2025), + color: colors.surfaceCardSecondary, borderRadius: BorderRadius.circular(12), border: Border.all( - color: AppColors.electricCyan.withValues(alpha: 0.3), + color: accentColor.withValues(alpha: 0.35), ), ), child: Text( '${comments.length}', - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 12, fontWeight: FontWeight.w700, - color: AppColors.electricCyan, + color: accentColor, ), ), ), const Spacer(), IconButton( - icon: const Icon(Icons.close_rounded, - color: Color(0xFF849396)), + icon: Icon( + Icons.close_rounded, + color: secondaryTextColor, + ), onPressed: () => Navigator.of(context).pop(), tooltip: 'Close', ), ], ), ), - const Divider(color: Color(0xFF1D2025), height: 1), + Divider(color: colors.surfaceBorder, height: 1), // Comment List or Empty Flexible( @@ -209,16 +219,16 @@ class _FeedCommentsBottomSheetState Icon( Icons.forum_outlined, size: 40, - color: colors.outline.withValues(alpha: 0.4), + color: secondaryTextColor, ), const SizedBox(height: 12), - const Text( + Text( 'No debrief comments yet', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 14, fontWeight: FontWeight.w600, - color: Colors.white70, + color: colors.textPrimary, ), ), const SizedBox(height: 4), @@ -228,7 +238,7 @@ class _FeedCommentsBottomSheetState style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 12, - color: colors.outline, + color: secondaryTextColor, ), ), ], @@ -246,10 +256,10 @@ class _FeedCommentsBottomSheetState return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xFF181C21), + color: colors.surfaceCardSecondary, borderRadius: BorderRadius.circular(12), border: Border.all( - color: const Color(0xFF272A30), + color: colors.surfaceBorder, ), ), child: Column( @@ -259,12 +269,11 @@ class _FeedCommentsBottomSheetState children: [ CircleAvatar( radius: 14, - backgroundColor: - const Color(0xFF272A30), - child: const Icon( + backgroundColor: colors.surfaceCardPrimary, + child: Icon( Icons.sports_motorsports_rounded, size: 16, - color: AppColors.electricCyan, + color: accentColor, ), ), const SizedBox(width: 8), @@ -275,12 +284,12 @@ class _FeedCommentsBottomSheetState child: Text( c.authorDisplayName, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 13, fontWeight: FontWeight.w600, - color: Colors.white, + color: colors.textPrimary, ), ), ), @@ -292,7 +301,7 @@ class _FeedCommentsBottomSheetState fontFamily: AppTypography.fontBody, fontSize: 11, - color: colors.outline, + color: secondaryTextColor, ), ), ], @@ -304,7 +313,7 @@ class _FeedCommentsBottomSheetState style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 10, - color: colors.outline, + color: secondaryTextColor, ), ), ], @@ -314,11 +323,11 @@ class _FeedCommentsBottomSheetState padding: const EdgeInsets.only(left: 36), child: Text( c.content, - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 13, height: 1.35, - color: Color(0xFFE0E2EA), + color: colors.textPrimary, ), ), ), @@ -329,7 +338,7 @@ class _FeedCommentsBottomSheetState ), ), - const Divider(color: Color(0xFF1D2025), height: 1), + Divider(color: colors.surfaceBorder, height: 1), // Comment Input Field (Glove Friendly) Padding( @@ -340,28 +349,28 @@ class _FeedCommentsBottomSheetState child: Container( height: 48, decoration: BoxDecoration( - color: const Color(0xFF181C21), + color: colors.surfaceCardSecondary, borderRadius: BorderRadius.circular(12), border: Border.all( - color: const Color(0xFF272A30), + color: colors.surfaceBorder, ), ), child: TextField( controller: _commentController, - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 13, - color: Colors.white, + color: colors.textPrimary, ), - decoration: const InputDecoration( + decoration: InputDecoration( hintText: 'Add comment or pace tip...', hintStyle: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 13, - color: Color(0xFF849396), + color: secondaryTextColor, ), border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( + contentPadding: const EdgeInsets.symmetric( horizontal: 14, vertical: 14, ), @@ -376,8 +385,8 @@ class _FeedCommentsBottomSheetState width: 48, child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: AppColors.electricCyan, - foregroundColor: const Color(0xFF00363D), + backgroundColor: sendBg, + foregroundColor: sendFg, padding: EdgeInsets.zero, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -387,12 +396,12 @@ class _FeedCommentsBottomSheetState onPressed: _isSubmitting ? null : () => _handlePostComment(), child: _isSubmitting - ? const SizedBox( + ? SizedBox( width: 18, height: 18, child: CircularProgressIndicator( strokeWidth: 2, - color: Color(0xFF00363D), + color: sendFg, ), ) : const Icon(Icons.send_rounded, size: 20), diff --git a/lib/features/social/feed/widgets/share_session_modal.dart b/lib/features/social/feed/widgets/share_session_modal.dart index b2da446..abba389 100644 --- a/lib/features/social/feed/widgets/share_session_modal.dart +++ b/lib/features/social/feed/widgets/share_session_modal.dart @@ -421,13 +421,13 @@ class _ShareSessionModalState extends ConsumerState { color: Colors.white, ), ), - Text( + const Text( 'Publish telemetry & route to community feed', overflow: TextOverflow.ellipsis, style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 12, - color: colors.outline, + color: AppColors.darkOnSurfaceVariant, ), ), ], diff --git a/test/community_social_feed_test.dart b/test/community_social_feed_test.dart index a5f3b90..e25ebee 100644 --- a/test/community_social_feed_test.dart +++ b/test/community_social_feed_test.dart @@ -768,4 +768,137 @@ void main() { }, ); }); + + group('BUG-002: Social Feed Typography & Theme Contrast Tests', () { + double contrastRatio(Color fg, Color bg) { + final l1 = fg.computeLuminance(); + final l2 = bg.computeLuminance(); + final brightest = l1 > l2 ? l1 : l2; + final darkest = l1 > l2 ? l2 : l1; + return (brightest + 0.05) / (darkest + 0.05); + } + + for (final isLight in [false, true]) { + testWidgets( + 'SocialFeedScreen and FeedCommentsBottomSheet maintain WCAG AA (>= 4.5:1) contrast in ${isLight ? "Light" : "Dark"} mode', + (tester) async { + await tester.binding.setSurfaceSize(const Size(412, 915)); + + final themeColors = isLight + ? MotorSignalThemeColors.light + : MotorSignalThemeColors.dark; + final baseTheme = isLight ? ThemeData.light() : ThemeData.dark(); + + final service = SocialFeedService(); + addTearDown(service.dispose); + + await tester.pumpWidget( + ProviderScope( + overrides: [ + socialFeedServiceProvider.overrideWithValue(service), + ], + child: MediaQuery( + data: const MediaQueryData( + size: Size(412, 915), + textScaler: TextScaler.linear(1.2), + ), + child: MaterialApp( + theme: baseTheme.copyWith( + extensions: [themeColors], + ), + home: const SocialFeedScreen(), + ), + ), + ), + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 50)); + + expect(tester.takeException(), isNull); + + // 1. AppBar title contrast against canvas surface + final appBarTitle = tester.widget(find.text('Community Feed')); + expect( + contrastRatio(appBarTitle.style!.color!, themeColors.surfaceCanvas), + greaterThanOrEqualTo(4.5), + ); + + // 2. Card primary text (@authorHandle and rideTitle) against card primary surface + final handleText = tester.widget(find.text('@ApexMarcus')); + expect( + contrastRatio( + handleText.style!.color!, + themeColors.surfaceCardPrimary, + ), + greaterThanOrEqualTo(4.5), + ); + + final rideTitleText = tester.widget( + find.text('Sunday Skyline & Pescadero Carve'), + ); + expect( + contrastRatio( + rideTitleText.style!.color!, + themeColors.surfaceCardPrimary, + ), + greaterThanOrEqualTo(4.5), + ); + + // 3. Card secondary text (clubName) against card primary surface + final clubText = tester.widget( + find.text('Bay Area Twisties Club'), + ); + expect( + contrastRatio( + clubText.style!.color!, + themeColors.surfaceCardPrimary, + ), + greaterThanOrEqualTo(4.5), + ); + + // 4. Filter tabs (selected vs unselected) contrast + final selectedFilterText = tester.widget( + find.text('All Activity'), + ); + final selectedFilterBg = isLight + ? const Color(0xFF0047B3) + : const Color(0xFF00E5FF); + expect( + contrastRatio(selectedFilterText.style!.color!, selectedFilterBg), + greaterThanOrEqualTo(4.5), + ); + + final unselectedFilterText = tester.widget( + find.text('Club Only'), + ); + expect( + contrastRatio( + unselectedFilterText.style!.color!, + themeColors.surfaceCardSecondary, + ), + greaterThanOrEqualTo(4.5), + ); + + // 5. Telemetry metric labels and values contrast against metric cell surface + final distLabel = tester.widget(find.text('DISTANCE').first); + expect( + contrastRatio( + distLabel.style!.color!, + themeColors.surfaceCardPrimary, + ), + greaterThanOrEqualTo(4.5), + ); + + final leanValue = tester.widget(find.text('51°').first); + expect( + contrastRatio( + leanValue.style!.color!, + themeColors.surfaceCardPrimary, + ), + greaterThanOrEqualTo(4.5), + ); + }, + ); + } + }); }