fix(social): standardize community feed typography and theme colors (#49)
This commit is contained in:
parent
265ee83e03
commit
1820739a49
File diff suppressed because it is too large
Load Diff
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue