fix(social): standardize community feed typography and theme colors (#49)

This commit is contained in:
Developer 2026-10-16 23:18:24 -04:00
parent 265ee83e03
commit 1820739a49
4 changed files with 429 additions and 249 deletions

File diff suppressed because it is too large Load Diff

View File

@ -100,6 +100,14 @@ class _FeedCommentsBottomSheetState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colors = context.motorColors; 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 bottomInset = MediaQuery.of(context).viewInsets.bottom;
final feedItems = ref.watch(socialFeedNotifierProvider); final feedItems = ref.watch(socialFeedNotifierProvider);
final livePost = feedItems.cast<SocialFeedItem?>().firstWhere( final livePost = feedItems.cast<SocialFeedItem?>().firstWhere(
@ -116,14 +124,14 @@ class _FeedCommentsBottomSheetState
), ),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF101419), color: colors.surfaceCardPrimary,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
border: Border.all(color: colors.outlineVariant.withValues(alpha: 0.5)), border: Border.all(color: colors.surfaceBorder),
boxShadow: const [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black87, color: colors.glowShadow,
blurRadius: 30, blurRadius: 30,
offset: Offset(0, -10), offset: const Offset(0, -10),
), ),
], ],
), ),
@ -139,7 +147,7 @@ class _FeedCommentsBottomSheetState
width: 44, width: 44,
height: 4, height: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.outlineVariant, color: colors.surfaceBorder,
borderRadius: BorderRadius.circular(2), borderRadius: BorderRadius.circular(2),
), ),
), ),
@ -149,9 +157,9 @@ class _FeedCommentsBottomSheetState
padding: const EdgeInsets.fromLTRB(20, 8, 12, 12), padding: const EdgeInsets.fromLTRB(20, 8, 12, 12),
child: Row( child: Row(
children: [ children: [
const Icon( Icon(
Icons.chat_bubble_outline_rounded, Icons.chat_bubble_outline_rounded,
color: AppColors.electricCyan, color: accentColor,
size: 20, size: 20,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@ -161,7 +169,7 @@ class _FeedCommentsBottomSheetState
fontFamily: AppTypography.fontHeadline, fontFamily: AppTypography.fontHeadline,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: Colors.white, color: colors.textPrimary,
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@ -169,33 +177,35 @@ class _FeedCommentsBottomSheetState
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 2), horizontal: 8, vertical: 2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF1D2025), color: colors.surfaceCardSecondary,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: AppColors.electricCyan.withValues(alpha: 0.3), color: accentColor.withValues(alpha: 0.35),
), ),
), ),
child: Text( child: Text(
'${comments.length}', '${comments.length}',
style: const TextStyle( style: TextStyle(
fontFamily: AppTypography.fontTelemetry, fontFamily: AppTypography.fontTelemetry,
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.electricCyan, color: accentColor,
), ),
), ),
), ),
const Spacer(), const Spacer(),
IconButton( IconButton(
icon: const Icon(Icons.close_rounded, icon: Icon(
color: Color(0xFF849396)), Icons.close_rounded,
color: secondaryTextColor,
),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
tooltip: 'Close', tooltip: 'Close',
), ),
], ],
), ),
), ),
const Divider(color: Color(0xFF1D2025), height: 1), Divider(color: colors.surfaceBorder, height: 1),
// Comment List or Empty // Comment List or Empty
Flexible( Flexible(
@ -209,16 +219,16 @@ class _FeedCommentsBottomSheetState
Icon( Icon(
Icons.forum_outlined, Icons.forum_outlined,
size: 40, size: 40,
color: colors.outline.withValues(alpha: 0.4), color: secondaryTextColor,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
const Text( Text(
'No debrief comments yet', 'No debrief comments yet',
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontHeadline, fontFamily: AppTypography.fontHeadline,
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.white70, color: colors.textPrimary,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@ -228,7 +238,7 @@ class _FeedCommentsBottomSheetState
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontBody, fontFamily: AppTypography.fontBody,
fontSize: 12, fontSize: 12,
color: colors.outline, color: secondaryTextColor,
), ),
), ),
], ],
@ -246,10 +256,10 @@ class _FeedCommentsBottomSheetState
return Container( return Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF181C21), color: colors.surfaceCardSecondary,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: const Color(0xFF272A30), color: colors.surfaceBorder,
), ),
), ),
child: Column( child: Column(
@ -259,12 +269,11 @@ class _FeedCommentsBottomSheetState
children: [ children: [
CircleAvatar( CircleAvatar(
radius: 14, radius: 14,
backgroundColor: backgroundColor: colors.surfaceCardPrimary,
const Color(0xFF272A30), child: Icon(
child: const Icon(
Icons.sports_motorsports_rounded, Icons.sports_motorsports_rounded,
size: 16, size: 16,
color: AppColors.electricCyan, color: accentColor,
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@ -275,12 +284,12 @@ class _FeedCommentsBottomSheetState
child: Text( child: Text(
c.authorDisplayName, c.authorDisplayName,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: const TextStyle( style: TextStyle(
fontFamily: fontFamily:
AppTypography.fontHeadline, AppTypography.fontHeadline,
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.white, color: colors.textPrimary,
), ),
), ),
), ),
@ -292,7 +301,7 @@ class _FeedCommentsBottomSheetState
fontFamily: fontFamily:
AppTypography.fontBody, AppTypography.fontBody,
fontSize: 11, fontSize: 11,
color: colors.outline, color: secondaryTextColor,
), ),
), ),
], ],
@ -304,7 +313,7 @@ class _FeedCommentsBottomSheetState
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontTelemetry, fontFamily: AppTypography.fontTelemetry,
fontSize: 10, fontSize: 10,
color: colors.outline, color: secondaryTextColor,
), ),
), ),
], ],
@ -314,11 +323,11 @@ class _FeedCommentsBottomSheetState
padding: const EdgeInsets.only(left: 36), padding: const EdgeInsets.only(left: 36),
child: Text( child: Text(
c.content, c.content,
style: const TextStyle( style: TextStyle(
fontFamily: AppTypography.fontBody, fontFamily: AppTypography.fontBody,
fontSize: 13, fontSize: 13,
height: 1.35, 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) // Comment Input Field (Glove Friendly)
Padding( Padding(
@ -340,28 +349,28 @@ class _FeedCommentsBottomSheetState
child: Container( child: Container(
height: 48, height: 48,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF181C21), color: colors.surfaceCardSecondary,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: const Color(0xFF272A30), color: colors.surfaceBorder,
), ),
), ),
child: TextField( child: TextField(
controller: _commentController, controller: _commentController,
style: const TextStyle( style: TextStyle(
fontFamily: AppTypography.fontBody, fontFamily: AppTypography.fontBody,
fontSize: 13, fontSize: 13,
color: Colors.white, color: colors.textPrimary,
), ),
decoration: const InputDecoration( decoration: InputDecoration(
hintText: 'Add comment or pace tip...', hintText: 'Add comment or pace tip...',
hintStyle: TextStyle( hintStyle: TextStyle(
fontFamily: AppTypography.fontBody, fontFamily: AppTypography.fontBody,
fontSize: 13, fontSize: 13,
color: Color(0xFF849396), color: secondaryTextColor,
), ),
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 14, horizontal: 14,
vertical: 14, vertical: 14,
), ),
@ -376,8 +385,8 @@ class _FeedCommentsBottomSheetState
width: 48, width: 48,
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.electricCyan, backgroundColor: sendBg,
foregroundColor: const Color(0xFF00363D), foregroundColor: sendFg,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@ -387,12 +396,12 @@ class _FeedCommentsBottomSheetState
onPressed: onPressed:
_isSubmitting ? null : () => _handlePostComment(), _isSubmitting ? null : () => _handlePostComment(),
child: _isSubmitting child: _isSubmitting
? const SizedBox( ? SizedBox(
width: 18, width: 18,
height: 18, height: 18,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
color: Color(0xFF00363D), color: sendFg,
), ),
) )
: const Icon(Icons.send_rounded, size: 20), : const Icon(Icons.send_rounded, size: 20),

View File

@ -421,13 +421,13 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
color: Colors.white, color: Colors.white,
), ),
), ),
Text( const Text(
'Publish telemetry & route to community feed', 'Publish telemetry & route to community feed',
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontBody, fontFamily: AppTypography.fontBody,
fontSize: 12, fontSize: 12,
color: colors.outline, color: AppColors.darkOnSurfaceVariant,
), ),
), ),
], ],

View File

@ -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<Text>(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<Text>(find.text('@ApexMarcus'));
expect(
contrastRatio(
handleText.style!.color!,
themeColors.surfaceCardPrimary,
),
greaterThanOrEqualTo(4.5),
);
final rideTitleText = tester.widget<Text>(
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<Text>(
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<Text>(
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<Text>(
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<Text>(find.text('DISTANCE').first);
expect(
contrastRatio(
distLabel.style!.color!,
themeColors.surfaceCardPrimary,
),
greaterThanOrEqualTo(4.5),
);
final leanValue = tester.widget<Text>(find.text('51°').first);
expect(
contrastRatio(
leanValue.style!.color!,
themeColors.surfaceCardPrimary,
),
greaterThanOrEqualTo(4.5),
);
},
);
}
});
} }