Fix/android followup 81 49 (#82)

* Fix coarse location labels and squad feed copy

* fix(social): align feed/share/inspector colors with app theme (#49)

* Avoid fabricated social feed locations

---------

Co-authored-by: Developer <dev@example.com>
This commit is contained in:
Billie Ponder 2026-10-17 15:13:09 -04:00 committed by GitHub
parent b005bb00ae
commit 2d1bfbeef6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 676 additions and 283 deletions

View File

@ -121,25 +121,29 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
); );
final isLight = Theme.of(context).brightness == Brightness.light; final isLight = Theme.of(context).brightness == Brightness.light;
final primaryTextColor =
isLight ? AppColors.lightOnSurface : AppColors.darkOnSurface;
final secondaryTextColor = final secondaryTextColor =
isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant;
final accentColor = final accentColor =
isLight ? AppColors.brandBlueStrong : colors.brandCyanBright; isLight ? AppColors.brandBlueStrong : AppColors.brandBlue;
final backgroundColor =
isLight ? AppColors.lightBackground : AppColors.darkBackground;
if (!widget.showAppBar) { if (!widget.showAppBar) {
return Container( return Container(
color: colors.surfaceCanvas, color: backgroundColor,
child: content, child: content,
); );
} }
return Scaffold( return Scaffold(
backgroundColor: colors.surfaceCanvas, backgroundColor: backgroundColor,
appBar: AppBar( appBar: AppBar(
backgroundColor: colors.surfaceCanvas, backgroundColor: backgroundColor,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: Icon(Icons.arrow_back_rounded, color: colors.textPrimary), icon: Icon(Icons.arrow_back_rounded, color: primaryTextColor),
onPressed: () { onPressed: () {
if (context.canPop()) { if (context.canPop()) {
context.pop(); context.pop();
@ -176,11 +180,11 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
letterSpacing: 0.2, letterSpacing: 0.2,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
Text( Text(
'SQUAD & CANYON TELEMETRY', 'SQUAD TELEMETRY',
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontTelemetry, fontFamily: AppTypography.fontTelemetry,
@ -235,14 +239,16 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
MotorSignalThemeColors colors, MotorSignalThemeColors colors,
) { ) {
final isLight = Theme.of(context).brightness == Brightness.light; final isLight = Theme.of(context).brightness == Brightness.light;
final primaryTextColor =
isLight ? AppColors.lightOnSurface : AppColors.darkOnSurface;
final secondaryTextColor = final secondaryTextColor =
isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant;
final accentColor = final accentColor =
isLight ? AppColors.brandBlueStrong : colors.brandCyanBright; isLight ? AppColors.brandBlueStrong : AppColors.brandBlue;
return Container( return Container(
padding: const EdgeInsets.fromLTRB(16, 10, 16, 8), padding: const EdgeInsets.fromLTRB(16, 10, 16, 8),
color: colors.surfaceCanvas, color: Colors.transparent,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
@ -256,7 +262,7 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardSecondary, color: colors.surfaceContainer,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all( border: Border.all(
color: accentColor.withValues(alpha: isLight ? 0.5 : 0.3), color: accentColor.withValues(alpha: isLight ? 0.5 : 0.3),
@ -276,7 +282,7 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
const SizedBox(width: 8), const SizedBox(width: 8),
Flexible( Flexible(
child: Text( child: Text(
'SQUAD & CANYON FEED', 'SQUAD FEED',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
@ -321,10 +327,10 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
Container( Container(
height: 42, height: 42,
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardPrimary, color: colors.surfaceContainer,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all( border: Border.all(
color: accentColor.withValues(alpha: 0.4), color: colors.outlineVariant,
), ),
), ),
child: TextField( child: TextField(
@ -333,7 +339,7 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontBody, fontFamily: AppTypography.fontBody,
fontSize: 13, fontSize: 13,
color: colors.textPrimary, color: primaryTextColor,
), ),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Search rider, route, bike, or region...', hintText: 'Search rider, route, bike, or region...',
@ -409,7 +415,7 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
Container( Container(
width: 1, width: 1,
height: 20, height: 20,
color: colors.surfaceBorder, color: colors.outlineVariant,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
_buildDisciplineChip( _buildDisciplineChip(
@ -420,7 +426,7 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
const SizedBox(width: 6), const SizedBox(width: 6),
_buildDisciplineChip( _buildDisciplineChip(
discipline: RidingDiscipline.canyonSport, discipline: RidingDiscipline.canyonSport,
label: '⚡ Canyon', label: '⚡ Sport',
isSelected: activeDiscipline == RidingDiscipline.canyonSport, isSelected: activeDiscipline == RidingDiscipline.canyonSport,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
@ -506,8 +512,8 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
}) { }) {
final colors = context.motorColors; final colors = context.motorColors;
final isLight = Theme.of(context).brightness == Brightness.light; final isLight = Theme.of(context).brightness == Brightness.light;
final selectedBg = isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; final selectedBg = isLight ? AppColors.brandBlueStrong : AppColors.brandBlue;
final selectedFg = isLight ? AppColors.onBrand : const Color(0xFF00272E); final selectedFg = AppColors.onBrand;
final unselectedFg = final unselectedFg =
isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant;
@ -523,10 +529,10 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
padding: const EdgeInsets.symmetric(horizontal: 10), padding: const EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? selectedBg : colors.surfaceCardSecondary, color: isSelected ? selectedBg : colors.surfaceContainer,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all( border: Border.all(
color: isSelected ? selectedBg : colors.surfaceBorder, color: isSelected ? selectedBg : colors.outlineVariant,
), ),
), ),
child: Text( child: Text(
@ -550,8 +556,8 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
}) { }) {
final colors = context.motorColors; final colors = context.motorColors;
final isLight = Theme.of(context).brightness == Brightness.light; final isLight = Theme.of(context).brightness == Brightness.light;
final selectedBg = isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; final selectedBg = isLight ? AppColors.brandBlueStrong : AppColors.brandBlue;
final selectedFg = isLight ? AppColors.onBrand : const Color(0xFF00272E); final selectedFg = AppColors.onBrand;
final unselectedFg = final unselectedFg =
isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant;
@ -566,10 +572,10 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
padding: const EdgeInsets.symmetric(horizontal: 10), padding: const EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? selectedBg : colors.surfaceCardSecondary, color: isSelected ? selectedBg : colors.surfaceContainer,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all( border: Border.all(
color: isSelected ? selectedBg : colors.surfaceBorder, color: isSelected ? selectedBg : colors.outlineVariant,
width: isSelected ? 1.2 : 1.0, width: isSelected ? 1.2 : 1.0,
), ),
), ),
@ -596,10 +602,12 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
final hasCustomFilters = final hasCustomFilters =
activeDiscipline != RidingDiscipline.all || searchQuery.isNotEmpty; activeDiscipline != RidingDiscipline.all || searchQuery.isNotEmpty;
final isLight = Theme.of(context).brightness == Brightness.light; final isLight = Theme.of(context).brightness == Brightness.light;
final primaryTextColor =
isLight ? AppColors.lightOnSurface : AppColors.darkOnSurface;
final secondaryTextColor = final secondaryTextColor =
isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant;
final accentColor = final accentColor =
isLight ? AppColors.brandBlueStrong : colors.brandCyanBright; isLight ? AppColors.brandBlueStrong : AppColors.brandBlue;
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
@ -632,7 +640,7 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
fontFamily: AppTypography.fontHeadline, fontFamily: AppTypography.fontHeadline,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
@ -725,8 +733,8 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
Widget _buildStickyShareButton(BuildContext context) { Widget _buildStickyShareButton(BuildContext context) {
final isLight = Theme.of(context).brightness == Brightness.light; final isLight = Theme.of(context).brightness == Brightness.light;
final buttonBg = isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; final buttonBg = isLight ? AppColors.brandBlueStrong : AppColors.brandBlue;
final buttonFg = isLight ? AppColors.onBrand : const Color(0xFF00272E); final buttonFg = AppColors.onBrand;
return Container( return Container(
height: 52, height: 52,
@ -800,13 +808,13 @@ Color _getDisciplineColor(RidingDiscipline discipline, {bool isLight = false}) {
case RidingDiscipline.advDualSport: case RidingDiscipline.advDualSport:
return AppColors.telemetryGreen; return AppColors.telemetryGreen;
case RidingDiscipline.touring: case RidingDiscipline.touring:
return AppColors.electricCyan; return AppColors.brandBlue;
case RidingDiscipline.trackDay: case RidingDiscipline.trackDay:
return AppColors.semanticDangerDark; return AppColors.semanticDangerDark;
case RidingDiscipline.cruiser: case RidingDiscipline.cruiser:
return AppColors.electricCyan; return AppColors.brandBlue;
case RidingDiscipline.all: case RidingDiscipline.all:
return AppColors.electricCyan; return AppColors.brandBlue;
} }
} }
@ -905,11 +913,14 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
Color accentColor, Color accentColor,
SocialFeedItem item, SocialFeedItem item,
) { ) {
final isLight = Theme.of(context).brightness == Brightness.light;
final primaryTextColor =
isLight ? AppColors.lightOnSurface : AppColors.darkOnSurface;
final isLandscape = final isLandscape =
MediaQuery.of(context).orientation == Orientation.landscape; MediaQuery.of(context).orientation == Orientation.landscape;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
backgroundColor: colors.surfaceCardSecondary, backgroundColor: colors.surfaceContainerHigh,
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
margin: EdgeInsets.fromLTRB( margin: EdgeInsets.fromLTRB(
16, 16,
@ -919,13 +930,13 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
), ),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
side: BorderSide(color: colors.surfaceBorder), side: BorderSide(color: colors.outlineVariant),
), ),
content: Row( content: Row(
children: [ children: [
Icon( const Icon(
Icons.download_done_rounded, Icons.download_done_rounded,
color: accentColor, color: AppColors.semanticSuccess,
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
@ -935,7 +946,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontBody, fontFamily: AppTypography.fontBody,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
), ),
@ -949,16 +960,18 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
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 isLight = Theme.of(context).brightness == Brightness.light;
final primaryTextColor =
isLight ? AppColors.lightOnSurface : AppColors.darkOnSurface;
final secondaryTextColor = final secondaryTextColor =
isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant;
final accentColor = final accentColor =
isLight ? AppColors.brandBlueStrong : colors.brandCyanBright; isLight ? AppColors.brandBlueStrong : AppColors.brandBlue;
final demoColor = final demoColor =
isLight ? AppColors.semanticWarningLight : AppColors.hazardAmber; isLight ? AppColors.semanticWarningLight : AppColors.hazardAmber;
final disciplineColor = final disciplineColor =
_getDisciplineColor(widget.item.discipline, isLight: isLight); _getDisciplineColor(widget.item.discipline, isLight: isLight);
final bumpedBg = isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; final bumpedBg = isLight ? AppColors.brandBlueStrong : AppColors.brandBlue;
final bumpedFg = isLight ? AppColors.onBrand : const Color(0xFF00272E); final bumpedFg = AppColors.onBrand;
final item = widget.item; final item = widget.item;
final user = ref.watch(currentUserProvider); final user = ref.watch(currentUserProvider);
@ -968,9 +981,9 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardPrimary, color: colors.surfaceContainer,
borderRadius: BorderRadius.circular(18), borderRadius: BorderRadius.circular(18),
border: Border.all(color: colors.surfaceBorder), border: Border.all(color: colors.outlineVariant),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: colors.glowShadow, color: colors.glowShadow,
@ -993,7 +1006,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
children: [ children: [
CircleAvatar( CircleAvatar(
radius: 22, radius: 22,
backgroundColor: colors.surfaceCardSecondary, backgroundColor: colors.surfaceContainerHigh,
child: Icon( child: Icon(
Icons.sports_motorsports_rounded, Icons.sports_motorsports_rounded,
color: accentColor, color: accentColor,
@ -1010,7 +1023,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
color: accentColor, color: accentColor,
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: colors.surfaceCardPrimary, color: colors.surfaceContainer,
width: 1.5, width: 1.5,
), ),
), ),
@ -1036,7 +1049,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
fontFamily: AppTypography.fontHeadline, fontFamily: AppTypography.fontHeadline,
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
if (item.isDemo) ...[ if (item.isDemo) ...[
@ -1123,7 +1136,9 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
], ],
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
'${_formatRelativeTime(item.timestamp)} • ${item.locationName}', item.locationName.isEmpty
? _formatRelativeTime(item.timestamp)
: '${_formatRelativeTime(item.timestamp)} • ${item.locationName}',
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontBody, fontFamily: AppTypography.fontBody,
fontSize: 12.5, fontSize: 12.5,
@ -1142,10 +1157,10 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
color: secondaryTextColor, color: secondaryTextColor,
size: 20, size: 20,
), ),
color: colors.surfaceCardSecondary, color: colors.surfaceContainerHigh,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
side: BorderSide(color: colors.surfaceBorder), side: BorderSide(color: colors.outlineVariant),
), ),
onSelected: (val) { onSelected: (val) {
if (val == 'export_gpx') { if (val == 'export_gpx') {
@ -1154,7 +1169,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
} }
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
backgroundColor: colors.surfaceCardSecondary, backgroundColor: colors.surfaceContainerHigh,
content: Row( content: Row(
children: [ children: [
Expanded( Expanded(
@ -1162,7 +1177,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
'Action: $val', 'Action: $val',
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(color: colors.textPrimary), style: TextStyle(color: primaryTextColor),
), ),
), ),
], ],
@ -1176,21 +1191,21 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
value: 'copy_link', value: 'copy_link',
child: Text( child: Text(
'Copy Route Link', 'Copy Route Link',
style: TextStyle(color: colors.textPrimary, fontSize: 13), style: TextStyle(color: primaryTextColor, fontSize: 13),
), ),
), ),
PopupMenuItem( PopupMenuItem(
value: 'export_gpx', value: 'export_gpx',
child: Text( child: Text(
'Export GPX File', 'Export GPX File',
style: TextStyle(color: colors.textPrimary, fontSize: 13), style: TextStyle(color: primaryTextColor, fontSize: 13),
), ),
), ),
PopupMenuItem( PopupMenuItem(
value: 'report', value: 'report',
child: Text( child: Text(
'Report Content', 'Report Content',
style: TextStyle(color: colors.dangerRed, fontSize: 13), style: TextStyle(color: AppColors.semanticDanger, fontSize: 13),
), ),
), ),
], ],
@ -1212,7 +1227,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
letterSpacing: -0.3, letterSpacing: -0.3,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
@ -1220,9 +1235,9 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
padding: padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 4), const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardSecondary, color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
border: Border.all(color: colors.surfaceBorder), border: Border.all(color: colors.outlineVariant),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -1242,7 +1257,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
fontFamily: AppTypography.fontTelemetry, fontFamily: AppTypography.fontTelemetry,
fontSize: 11.5, fontSize: 11.5,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
), ),
@ -1280,9 +1295,9 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
child: Container( child: Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardSecondary, color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
border: Border.all(color: colors.surfaceBorder), border: Border.all(color: colors.outlineVariant),
), ),
child: Column( child: Column(
children: [ children: [
@ -1293,7 +1308,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
label: 'DISTANCE', label: 'DISTANCE',
value: item.distanceMiles.toStringAsFixed(1), value: item.distanceMiles.toStringAsFixed(1),
unit: 'MI', unit: 'MI',
valueColor: colors.textPrimary, valueColor: primaryTextColor,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
_buildMetricCell( _buildMetricCell(
@ -1301,7 +1316,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
label: 'DURATION', label: 'DURATION',
value: _formatDuration(item.durationSeconds), value: _formatDuration(item.durationSeconds),
unit: '', unit: '',
valueColor: colors.textPrimary, valueColor: primaryTextColor,
), ),
], ],
), ),
@ -1313,7 +1328,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
label: 'MAX LEAN', label: 'MAX LEAN',
value: '${item.maxLeanAngleDeg.toStringAsFixed(0)}°', value: '${item.maxLeanAngleDeg.toStringAsFixed(0)}°',
unit: item.maxLeanSide, unit: item.maxLeanSide,
valueColor: accentColor, valueColor: primaryTextColor,
indicatorColor: accentColor, indicatorColor: accentColor,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
@ -1324,7 +1339,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
unit: item.paceFactor.split(' ').length > 1 unit: item.paceFactor.split(' ').length > 1
? item.paceFactor.split(' ').sublist(1).join(' ') ? item.paceFactor.split(' ').sublist(1).join(' ')
: '', : '',
valueColor: accentColor, valueColor: primaryTextColor,
), ),
], ],
), ),
@ -1365,12 +1380,12 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
decoration: BoxDecoration( decoration: BoxDecoration(
color: isBumped color: isBumped
? bumpedBg ? bumpedBg
: colors.surfaceCardSecondary, : colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: isBumped color: isBumped
? bumpedBg ? bumpedBg
: colors.surfaceBorder, : colors.outlineVariant,
), ),
), ),
child: Row( child: Row(
@ -1388,7 +1403,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: isBumped color: isBumped
? bumpedFg ? bumpedFg
: colors.textPrimary, : primaryTextColor,
), ),
), ),
), ),
@ -1410,9 +1425,9 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
child: Container( child: Container(
height: 48, height: 48,
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardSecondary, color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: colors.surfaceBorder), border: Border.all(color: colors.outlineVariant),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -1429,7 +1444,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
fontFamily: AppTypography.fontTelemetry, fontFamily: AppTypography.fontTelemetry,
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
], ],
@ -1450,14 +1465,14 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
width: 48, width: 48,
height: 48, height: 48,
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardSecondary, color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: colors.surfaceBorder), border: Border.all(color: colors.outlineVariant),
), ),
child: Icon( child: Icon(
Icons.download_rounded, Icons.download_rounded,
size: 20, size: 20,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
), ),
@ -1476,7 +1491,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
height: 44, height: 44,
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardSecondary, color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: accentColor.withValues(alpha: 0.35), color: accentColor.withValues(alpha: 0.35),
@ -1533,9 +1548,9 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardPrimary, color: colors.surfaceContainer,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all(color: colors.surfaceBorder, width: 0.8), border: Border.all(color: colors.outlineVariant, width: 0.8),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -1611,13 +1626,27 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
} }
Widget _buildRouteTrackModule(BuildContext context, SocialFeedItem item) { Widget _buildRouteTrackModule(BuildContext context, SocialFeedItem item) {
final colors = context.motorColors;
final isLight = Theme.of(context).brightness == Brightness.light;
final primaryTextColor =
isLight ? AppColors.lightOnSurface : AppColors.darkOnSurface;
final secondaryTextColor =
isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant;
final accentColor =
isLight ? AppColors.brandBlueStrong : AppColors.brandBlue;
final containerBg =
isLight ? AppColors.lightSurfaceContainerHighest : AppColors.darkSurface;
final borderColor = colors.outlineVariant;
final overlayBg = (isLight ? AppColors.lightSurface : AppColors.darkSurfaceContainer)
.withValues(alpha: 0.88);
final hasValidTrack = item.hasRecordedRoute; final hasValidTrack = item.hasRecordedRoute;
return Container( return Container(
height: 160, height: 160,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF0B0E13), color: containerBg,
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFF181C21)), border: Border.all(color: borderColor),
), ),
child: Stack( child: Stack(
children: [ children: [
@ -1629,6 +1658,11 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
painter: _FeedRoutePainter( painter: _FeedRoutePainter(
points: hasValidTrack ? item.routePoints : const [], points: hasValidTrack ? item.routePoints : const [],
maxLean: item.maxLeanAngleDeg, maxLean: item.maxLeanAngleDeg,
accentColor: accentColor,
gridColor: isLight
? colors.outlineVariant
: AppColors.darkOutlineVariant,
finishColor: secondaryTextColor,
), ),
), ),
), ),
@ -1645,21 +1679,21 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
vertical: 6, vertical: 6,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF101419).withValues(alpha: 0.88), color: overlayBg,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all( border: Border.all(
color: const Color(0xFF2A323D), color: borderColor,
), ),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: const [ children: [
Icon( Icon(
Icons.gps_off_rounded, Icons.gps_off_rounded,
size: 14, size: 14,
color: Color(0xFF94A3B8), color: secondaryTextColor,
), ),
SizedBox(width: 6), const SizedBox(width: 6),
Flexible( Flexible(
child: Text( child: Text(
'NO GPS TRACK RECORDED', 'NO GPS TRACK RECORDED',
@ -1670,7 +1704,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
letterSpacing: 0.5, letterSpacing: 0.5,
color: Color(0xFFCBD5E1), color: primaryTextColor,
), ),
), ),
), ),
@ -1687,28 +1721,28 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF1D2025).withValues(alpha: 0.85), color: overlayBg,
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
border: Border.all( border: Border.all(
color: AppColors.electricCyan.withValues(alpha: 0.2), color: accentColor.withValues(alpha: 0.2),
), ),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon( Icon(
Icons.landscape_rounded, Icons.landscape_rounded,
size: 13, size: 13,
color: AppColors.electricCyan, color: accentColor,
), ),
const SizedBox(width: 5), const SizedBox(width: 5),
Text( Text(
'ELV +${item.elevationFt} FT', 'ELV +${item.elevationFt} FT',
style: const TextStyle( style: TextStyle(
fontFamily: AppTypography.fontTelemetry, fontFamily: AppTypography.fontTelemetry,
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: Color(0xFFE0E2EA), color: primaryTextColor,
), ),
), ),
], ],
@ -1725,25 +1759,25 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
padding: padding:
const EdgeInsets.symmetric(horizontal: 7, vertical: 3), const EdgeInsets.symmetric(horizontal: 7, vertical: 3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF101419).withValues(alpha: 0.85), color: overlayBg,
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
border: Border.all( border: Border.all(
color: AppColors.electricCyan.withValues(alpha: 0.2), color: accentColor.withValues(alpha: 0.2),
), ),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: const [ children: [
Icon(Icons.verified_rounded, Icon(Icons.verified_rounded,
size: 12, color: AppColors.electricCyan), size: 12, color: accentColor),
SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
'GPS VERIFIED', 'GPS VERIFIED',
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontTelemetry, fontFamily: AppTypography.fontTelemetry,
fontSize: 9, fontSize: 9,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
color: AppColors.electricCyan, color: accentColor,
), ),
), ),
], ],
@ -1757,12 +1791,24 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
} }
Widget _buildScenicPhotoModule(BuildContext context, SocialFeedItem item) { Widget _buildScenicPhotoModule(BuildContext context, SocialFeedItem item) {
final colors = context.motorColors;
final isLight = Theme.of(context).brightness == Brightness.light;
final secondaryTextColor =
isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant;
final accentColor =
isLight ? AppColors.brandBlueStrong : AppColors.brandBlue;
final containerBg =
isLight ? AppColors.lightSurfaceContainerHighest : AppColors.darkSurface;
final borderColor = colors.outlineVariant;
final overlayBg = (isLight ? AppColors.lightSurface : AppColors.darkSurfaceContainer)
.withValues(alpha: 0.88);
return Container( return Container(
height: 180, height: 180,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF0B0E13), color: containerBg,
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFF181C21)), border: Border.all(color: borderColor),
), ),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
@ -1771,22 +1817,28 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
children: [ children: [
// Atmospheric gradient container mimicking canyon overlook // Atmospheric gradient container mimicking canyon overlook
Container( Container(
decoration: const BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: isLight
Color(0xFF1E293B), ? [
Color(0xFF0F172A), AppColors.lightSurfaceContainerHigh,
Color(0xFF020617), AppColors.lightSurfaceContainerHighest,
], AppColors.lightOutlineVariant,
]
: [
AppColors.darkSurfaceContainer,
AppColors.darkSurface,
AppColors.darkBackground,
],
), ),
), ),
child: const Center( child: Center(
child: Icon( child: Icon(
Icons.landscape_rounded, Icons.landscape_rounded,
size: 64, size: 64,
color: Color(0xFF334155), color: secondaryTextColor,
), ),
), ),
), ),
@ -1822,25 +1874,25 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4), horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF101419).withValues(alpha: 0.85), color: overlayBg,
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon( Icon(
Icons.photo_camera_rounded, Icons.photo_camera_rounded,
size: 13, size: 13,
color: AppColors.electricCyan, color: accentColor,
), ),
const SizedBox(width: 5), const SizedBox(width: 5),
Text( Text(
item.photoCaption ?? 'Scenic Overlook', item.photoCaption ?? 'Scenic Overlook',
style: const TextStyle( style: TextStyle(
fontFamily: AppTypography.fontHeadline, fontFamily: AppTypography.fontHeadline,
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.white, color: isLight ? AppColors.lightOnSurface : Colors.white,
), ),
), ),
], ],
@ -1851,25 +1903,25 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4), horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF101419).withValues(alpha: 0.85), color: overlayBg,
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: const [ children: [
Icon( Icon(
Icons.verified_rounded, Icons.verified_rounded,
size: 13, size: 13,
color: AppColors.electricCyan, color: accentColor,
), ),
SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
'GPS VERIFIED', 'GPS VERIFIED',
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontTelemetry, fontFamily: AppTypography.fontTelemetry,
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
color: AppColors.electricCyan, color: accentColor,
), ),
), ),
], ],
@ -1891,10 +1943,16 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
class _FeedRoutePainter extends CustomPainter { class _FeedRoutePainter extends CustomPainter {
final List<AppLatLng> points; final List<AppLatLng> points;
final double maxLean; final double maxLean;
final Color accentColor;
final Color gridColor;
final Color finishColor;
const _FeedRoutePainter({ const _FeedRoutePainter({
required this.points, required this.points,
required this.maxLean, required this.maxLean,
required this.accentColor,
required this.gridColor,
required this.finishColor,
}); });
@override @override
@ -1904,7 +1962,7 @@ class _FeedRoutePainter extends CustomPainter {
// 1. Grid lines matrix overlay // 1. Grid lines matrix overlay
final gridPaint = Paint() final gridPaint = Paint()
..color = const Color(0xFF1D2025) ..color = gridColor
..strokeWidth = 0.8; ..strokeWidth = 0.8;
const gridSize = 24.0; const gridSize = 24.0;
@ -1958,9 +2016,9 @@ class _FeedRoutePainter extends CustomPainter {
path.lineTo(mappedPoints[i].dx, mappedPoints[i].dy); path.lineTo(mappedPoints[i].dx, mappedPoints[i].dy);
} }
// Cyan glow blur // Glow blur
final glowPaint = Paint() final glowPaint = Paint()
..color = AppColors.electricCyan.withValues(alpha: 0.25) ..color = accentColor.withValues(alpha: 0.25)
..strokeWidth = 8 ..strokeWidth = 8
..style = PaintingStyle.stroke ..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round ..strokeCap = StrokeCap.round
@ -1969,7 +2027,7 @@ class _FeedRoutePainter extends CustomPainter {
// Active trail // Active trail
final trailPaint = Paint() final trailPaint = Paint()
..color = const Color(0xFFC3F5FF) ..color = accentColor
..strokeWidth = 3.5 ..strokeWidth = 3.5
..style = PaintingStyle.stroke ..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round ..strokeCap = StrokeCap.round
@ -1981,10 +2039,10 @@ class _FeedRoutePainter extends CustomPainter {
final apexOffset = mappedPoints[mappedPoints.length ~/ 2]; final apexOffset = mappedPoints[mappedPoints.length ~/ 2];
// Start marker with ring // Start marker with ring
final startPaint = Paint()..color = AppColors.electricCyan; final startPaint = Paint()..color = accentColor;
canvas.drawCircle(startOffset, 4.5, startPaint); canvas.drawCircle(startOffset, 4.5, startPaint);
final ringPaint = Paint() final ringPaint = Paint()
..color = AppColors.electricCyan.withValues(alpha: 0.6) ..color = accentColor.withValues(alpha: 0.6)
..style = PaintingStyle.stroke ..style = PaintingStyle.stroke
..strokeWidth = 1.5; ..strokeWidth = 1.5;
canvas.drawCircle(startOffset, 8.0, ringPaint); canvas.drawCircle(startOffset, 8.0, ringPaint);
@ -1992,16 +2050,16 @@ class _FeedRoutePainter extends CustomPainter {
// Apex marker (only if maxLean > 0) // Apex marker (only if maxLean > 0)
final textPainter = TextPainter(textDirection: TextDirection.ltr); final textPainter = TextPainter(textDirection: TextDirection.ltr);
if (maxLean > 0) { if (maxLean > 0) {
final apexPaint = Paint()..color = AppColors.electricCyan; final apexPaint = Paint()..color = accentColor;
canvas.drawCircle(apexOffset, 4.0, apexPaint); canvas.drawCircle(apexOffset, 4.0, apexPaint);
textPainter.text = TextSpan( textPainter.text = TextSpan(
text: '${maxLean.toStringAsFixed(0)}° APEX', text: '${maxLean.toStringAsFixed(0)}° APEX',
style: const TextStyle( style: TextStyle(
fontFamily: AppTypography.fontTelemetry, fontFamily: AppTypography.fontTelemetry,
fontSize: 9, fontSize: 9,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
color: Color(0xFFC3F5FF), color: accentColor,
), ),
); );
textPainter.layout(); textPainter.layout();
@ -2015,16 +2073,16 @@ class _FeedRoutePainter extends CustomPainter {
} }
// Finish marker // Finish marker
final finishPaint = Paint()..color = const Color(0xFFBAC9CC); final finishPaint = Paint()..color = finishColor;
canvas.drawCircle(finishOffset, 4.0, finishPaint); canvas.drawCircle(finishOffset, 4.0, finishPaint);
textPainter.text = const TextSpan( textPainter.text = TextSpan(
text: 'FINISH', text: 'FINISH',
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontTelemetry, fontFamily: AppTypography.fontTelemetry,
fontSize: 9, fontSize: 9,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: Color(0xFFBAC9CC), color: finishColor,
), ),
); );
textPainter.layout(); textPainter.layout();
@ -2039,6 +2097,10 @@ class _FeedRoutePainter extends CustomPainter {
@override @override
bool shouldRepaint(covariant _FeedRoutePainter oldDelegate) { bool shouldRepaint(covariant _FeedRoutePainter oldDelegate) {
return oldDelegate.maxLean != maxLean || oldDelegate.points != points; return oldDelegate.maxLean != maxLean ||
oldDelegate.points != points ||
oldDelegate.accentColor != accentColor ||
oldDelegate.gridColor != gridColor ||
oldDelegate.finishColor != finishColor;
} }
} }

View File

@ -103,10 +103,12 @@ class _FeedCommentsBottomSheetState
final isLight = Theme.of(context).brightness == Brightness.light; final isLight = Theme.of(context).brightness == Brightness.light;
final secondaryTextColor = final secondaryTextColor =
isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant;
final primaryTextColor =
isLight ? AppColors.lightOnSurface : AppColors.darkOnSurface;
final accentColor = final accentColor =
isLight ? AppColors.brandBlueStrong : colors.brandCyanBright; isLight ? AppColors.brandBlueStrong : AppColors.brandBlue;
final sendBg = isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; final sendBg = isLight ? AppColors.brandBlueStrong : AppColors.brandBlue;
final sendFg = isLight ? AppColors.onBrand : const Color(0xFF00363D); final sendFg = AppColors.onBrand;
final bottomInset = MediaQuery.of(context).viewInsets.bottom; final bottomInset = MediaQuery.of(context).viewInsets.bottom;
final feedItems = ref.watch(socialFeedNotifierProvider); final feedItems = ref.watch(socialFeedNotifierProvider);
@ -124,9 +126,9 @@ class _FeedCommentsBottomSheetState
), ),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardPrimary, color: colors.surfaceContainer,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
border: Border.all(color: colors.surfaceBorder), border: Border.all(color: colors.outlineVariant),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: colors.glowShadow, color: colors.glowShadow,
@ -147,7 +149,7 @@ class _FeedCommentsBottomSheetState
width: 44, width: 44,
height: 4, height: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceBorder, color: colors.outlineVariant,
borderRadius: BorderRadius.circular(2), borderRadius: BorderRadius.circular(2),
), ),
), ),
@ -169,7 +171,7 @@ class _FeedCommentsBottomSheetState
fontFamily: AppTypography.fontHeadline, fontFamily: AppTypography.fontHeadline,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@ -177,7 +179,7 @@ class _FeedCommentsBottomSheetState
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 2), horizontal: 8, vertical: 2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardSecondary, color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: accentColor.withValues(alpha: 0.35), color: accentColor.withValues(alpha: 0.35),
@ -205,7 +207,7 @@ class _FeedCommentsBottomSheetState
], ],
), ),
), ),
Divider(color: colors.surfaceBorder, height: 1), Divider(color: colors.outlineVariant, height: 1),
// Comment List or Empty // Comment List or Empty
Flexible( Flexible(
@ -228,7 +230,7 @@ class _FeedCommentsBottomSheetState
fontFamily: AppTypography.fontHeadline, fontFamily: AppTypography.fontHeadline,
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@ -256,10 +258,10 @@ class _FeedCommentsBottomSheetState
return Container( return Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardSecondary, color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: colors.surfaceBorder, color: colors.outlineVariant,
), ),
), ),
child: Column( child: Column(
@ -269,7 +271,7 @@ class _FeedCommentsBottomSheetState
children: [ children: [
CircleAvatar( CircleAvatar(
radius: 14, radius: 14,
backgroundColor: colors.surfaceCardPrimary, backgroundColor: colors.surfaceContainer,
child: Icon( child: Icon(
Icons.sports_motorsports_rounded, Icons.sports_motorsports_rounded,
size: 16, size: 16,
@ -289,7 +291,7 @@ class _FeedCommentsBottomSheetState
AppTypography.fontHeadline, AppTypography.fontHeadline,
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
), ),
@ -327,7 +329,7 @@ class _FeedCommentsBottomSheetState
fontFamily: AppTypography.fontBody, fontFamily: AppTypography.fontBody,
fontSize: 13, fontSize: 13,
height: 1.35, height: 1.35,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
), ),
@ -338,7 +340,7 @@ class _FeedCommentsBottomSheetState
), ),
), ),
Divider(color: colors.surfaceBorder, height: 1), Divider(color: colors.outlineVariant, height: 1),
// Comment Input Field (Glove Friendly) // Comment Input Field (Glove Friendly)
Padding( Padding(
@ -349,10 +351,10 @@ class _FeedCommentsBottomSheetState
child: Container( child: Container(
height: 48, height: 48,
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardSecondary, color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: colors.surfaceBorder, color: colors.outlineVariant,
), ),
), ),
child: TextField( child: TextField(
@ -360,7 +362,7 @@ class _FeedCommentsBottomSheetState
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontBody, fontFamily: AppTypography.fontBody,
fontSize: 13, fontSize: 13,
color: colors.textPrimary, color: primaryTextColor,
), ),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Add comment or pace tip...', hintText: 'Add comment or pace tip...',

View File

@ -61,8 +61,10 @@ class _FeedTelemetryInspectorModalState
final isLight = Theme.of(context).brightness == Brightness.light; final isLight = Theme.of(context).brightness == Brightness.light;
final secondaryTextColor = final secondaryTextColor =
isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant;
final primaryTextColor =
isLight ? AppColors.lightOnSurface : AppColors.darkOnSurface;
final accentColor = final accentColor =
isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; isLight ? AppColors.brandBlueStrong : AppColors.brandBlue;
final item = widget.feedItem; final item = widget.feedItem;
final mediaQuery = MediaQuery.of(context); final mediaQuery = MediaQuery.of(context);
final maxHeight = mediaQuery.size.height * 0.90; final maxHeight = mediaQuery.size.height * 0.90;
@ -70,12 +72,12 @@ class _FeedTelemetryInspectorModalState
return Container( return Container(
constraints: BoxConstraints(maxHeight: maxHeight), constraints: BoxConstraints(maxHeight: maxHeight),
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardPrimary, color: colors.surfaceContainer,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
border: Border( border: Border(
top: BorderSide(color: colors.surfaceBorder, width: 1.5), top: BorderSide(color: colors.outlineVariant, width: 1.5),
left: BorderSide(color: colors.surfaceBorder, width: 1.5), left: BorderSide(color: colors.outlineVariant, width: 1.5),
right: BorderSide(color: colors.surfaceBorder, width: 1.5), right: BorderSide(color: colors.outlineVariant, width: 1.5),
), ),
), ),
child: SafeArea( child: SafeArea(
@ -91,7 +93,7 @@ class _FeedTelemetryInspectorModalState
width: 40, width: 40,
height: 4, height: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceBorder, color: colors.outlineVariant,
borderRadius: BorderRadius.circular(2), borderRadius: BorderRadius.circular(2),
), ),
), ),
@ -132,7 +134,7 @@ class _FeedTelemetryInspectorModalState
fontFamily: AppTypography.fontHeadline, fontFamily: AppTypography.fontHeadline,
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
), ),
@ -191,7 +193,7 @@ class _FeedTelemetryInspectorModalState
), ),
), ),
Divider(color: colors.surfaceBorder, height: 1), Divider(color: colors.outlineVariant, height: 1),
// Scrollable Content: Metrics + Graph + Replay Trigger // Scrollable Content: Metrics + Graph + Replay Trigger
Flexible( Flexible(
@ -201,23 +203,23 @@ class _FeedTelemetryInspectorModalState
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// 2x3 Telemetry Insets Grid // 2x3 Telemetry Insets Grid
_buildMetricsGrid(item, colors, isLight, secondaryTextColor, accentColor), _buildMetricsGrid(item, colors, isLight, secondaryTextColor, accentColor, primaryTextColor),
const SizedBox(height: 18), const SizedBox(height: 18),
// Trace Selector Tabs // Trace Selector Tabs
_buildTraceSelector(colors, secondaryTextColor, accentColor), _buildTraceSelector(colors, secondaryTextColor, accentColor, primaryTextColor),
const SizedBox(height: 12), const SizedBox(height: 12),
// Interactive Telemetry Graph Canvas // Interactive Telemetry Graph Canvas
_buildInteractiveGraphCard(item, colors), _buildInteractiveGraphCard(item, colors, isLight, secondaryTextColor, accentColor, primaryTextColor),
const SizedBox(height: 16), const SizedBox(height: 16),
// Scrubber Details Pill // Scrubber Details Pill
_buildScrubberReadout(item, colors, secondaryTextColor, accentColor), _buildScrubberReadout(item, colors, secondaryTextColor, accentColor, primaryTextColor),
const SizedBox(height: 20), const SizedBox(height: 20),
// Launch Full Replay Button // Launch Full Replay Button
_buildReplayButton(context, colors, accentColor), _buildReplayButton(context, colors, accentColor, primaryTextColor),
], ],
), ),
), ),
@ -234,18 +236,19 @@ class _FeedTelemetryInspectorModalState
bool isLight, bool isLight,
Color secondaryTextColor, Color secondaryTextColor,
Color accentColor, Color accentColor,
Color primaryTextColor,
) { ) {
final successColor = final successColor =
isLight ? AppColors.semanticSuccessLight : const Color(0xFF00E676); isLight ? AppColors.semanticSuccessLight : AppColors.semanticSuccess;
final warnColor = final warnColor =
isLight ? AppColors.semanticWarningLight : AppColors.hazardAmber; isLight ? AppColors.semanticWarningLight : AppColors.hazardAmber;
return Container( return Container(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardSecondary, color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: colors.surfaceBorder), border: Border.all(color: colors.outlineVariant),
), ),
child: Column( child: Column(
children: [ children: [
@ -261,24 +264,24 @@ class _FeedTelemetryInspectorModalState
icon: Icons.rotate_right_rounded, icon: Icons.rotate_right_rounded,
), ),
), ),
Container(width: 1, height: 48, color: colors.surfaceBorder), Container(width: 1, height: 48, color: colors.outlineVariant),
Expanded( Expanded(
child: _buildMetricCell( child: _buildMetricCell(
label: 'DISTANCE', label: 'DISTANCE',
value: item.distanceMiles.toStringAsFixed(1), value: item.distanceMiles.toStringAsFixed(1),
subtext: 'MILES', subtext: 'MILES',
accentColor: colors.textPrimary, accentColor: primaryTextColor,
secondaryTextColor: secondaryTextColor, secondaryTextColor: secondaryTextColor,
icon: Icons.route_rounded, icon: Icons.route_rounded,
), ),
), ),
Container(width: 1, height: 48, color: colors.surfaceBorder), Container(width: 1, height: 48, color: colors.outlineVariant),
Expanded( Expanded(
child: _buildMetricCell( child: _buildMetricCell(
label: 'DURATION', label: 'DURATION',
value: _formatDuration(item.durationSeconds), value: _formatDuration(item.durationSeconds),
subtext: 'MOVING', subtext: 'MOVING',
accentColor: colors.textPrimary, accentColor: primaryTextColor,
secondaryTextColor: secondaryTextColor, secondaryTextColor: secondaryTextColor,
icon: Icons.timer_outlined, icon: Icons.timer_outlined,
), ),
@ -286,7 +289,7 @@ class _FeedTelemetryInspectorModalState
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Divider(color: colors.surfaceBorder, height: 1), Divider(color: colors.outlineVariant, height: 1),
const SizedBox(height: 12), const SizedBox(height: 12),
Row( Row(
children: [ children: [
@ -302,18 +305,18 @@ class _FeedTelemetryInspectorModalState
icon: Icons.speed_rounded, icon: Icons.speed_rounded,
), ),
), ),
Container(width: 1, height: 48, color: colors.surfaceBorder), Container(width: 1, height: 48, color: colors.outlineVariant),
Expanded( Expanded(
child: _buildMetricCell( child: _buildMetricCell(
label: 'ELEVATION', label: 'ELEVATION',
value: '+${item.elevationFt}', value: '+${item.elevationFt}',
subtext: 'FEET GAIN', subtext: 'FEET GAIN',
accentColor: colors.textPrimary, accentColor: primaryTextColor,
secondaryTextColor: secondaryTextColor, secondaryTextColor: secondaryTextColor,
icon: Icons.terrain_rounded, icon: Icons.terrain_rounded,
), ),
), ),
Container(width: 1, height: 48, color: colors.surfaceBorder), Container(width: 1, height: 48, color: colors.outlineVariant),
Expanded( Expanded(
child: _buildMetricCell( child: _buildMetricCell(
label: 'HAZARDS', label: 'HAZARDS',
@ -396,13 +399,14 @@ class _FeedTelemetryInspectorModalState
MotorSignalThemeColors colors, MotorSignalThemeColors colors,
Color secondaryTextColor, Color secondaryTextColor,
Color accentColor, Color accentColor,
Color primaryTextColor,
) { ) {
return Container( return Container(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardSecondary, color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: colors.surfaceBorder), border: Border.all(color: colors.outlineVariant),
), ),
child: Row( child: Row(
children: [ children: [
@ -413,6 +417,7 @@ class _FeedTelemetryInspectorModalState
colors: colors, colors: colors,
secondaryTextColor: secondaryTextColor, secondaryTextColor: secondaryTextColor,
accentColor: accentColor, accentColor: accentColor,
primaryTextColor: primaryTextColor,
), ),
_buildTraceTabItem( _buildTraceTabItem(
tab: _InspectorTraceTab.speedGForce, tab: _InspectorTraceTab.speedGForce,
@ -421,6 +426,7 @@ class _FeedTelemetryInspectorModalState
colors: colors, colors: colors,
secondaryTextColor: secondaryTextColor, secondaryTextColor: secondaryTextColor,
accentColor: accentColor, accentColor: accentColor,
primaryTextColor: primaryTextColor,
), ),
_buildTraceTabItem( _buildTraceTabItem(
tab: _InspectorTraceTab.elevation, tab: _InspectorTraceTab.elevation,
@ -429,6 +435,7 @@ class _FeedTelemetryInspectorModalState
colors: colors, colors: colors,
secondaryTextColor: secondaryTextColor, secondaryTextColor: secondaryTextColor,
accentColor: accentColor, accentColor: accentColor,
primaryTextColor: primaryTextColor,
), ),
], ],
), ),
@ -442,6 +449,7 @@ class _FeedTelemetryInspectorModalState
required MotorSignalThemeColors colors, required MotorSignalThemeColors colors,
required Color secondaryTextColor, required Color secondaryTextColor,
required Color accentColor, required Color accentColor,
required Color primaryTextColor,
}) { }) {
final isSelected = _selectedTab == tab; final isSelected = _selectedTab == tab;
return Expanded( return Expanded(
@ -452,7 +460,7 @@ class _FeedTelemetryInspectorModalState
duration: const Duration(milliseconds: 180), duration: const Duration(milliseconds: 180),
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? colors.surfaceCardPrimary : Colors.transparent, color: isSelected ? colors.surfaceContainer : Colors.transparent,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: isSelected border: isSelected
? Border.all(color: accentColor.withValues(alpha: 0.4)) ? Border.all(color: accentColor.withValues(alpha: 0.4))
@ -476,7 +484,7 @@ class _FeedTelemetryInspectorModalState
fontSize: 10, fontSize: 10,
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600, fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600,
letterSpacing: 0.3, letterSpacing: 0.3,
color: isSelected ? colors.textPrimary : secondaryTextColor, color: isSelected ? primaryTextColor : secondaryTextColor,
), ),
), ),
), ),
@ -490,14 +498,20 @@ class _FeedTelemetryInspectorModalState
Widget _buildInteractiveGraphCard( Widget _buildInteractiveGraphCard(
SocialFeedItem item, SocialFeedItem item,
MotorSignalThemeColors colors, MotorSignalThemeColors colors,
bool isLight,
Color secondaryTextColor,
Color accentColor,
Color primaryTextColor,
) { ) {
final hasRecorded = item.hasRecordedTelemetry; final hasRecorded = item.hasRecordedTelemetry;
return Container( return Container(
height: 220, height: 220,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF0C1015), color: isLight
? AppColors.lightSurfaceContainerHighest
: AppColors.darkSurface,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF222B38)), border: Border.all(color: colors.outlineVariant),
), ),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
@ -533,6 +547,12 @@ class _FeedTelemetryInspectorModalState
tab: _selectedTab, tab: _selectedTab,
item: item, item: item,
scrubFraction: _scrubFraction, scrubFraction: _scrubFraction,
accentColor: accentColor,
gridColor: colors.outlineVariant,
baselineColor: colors.outline,
badgeBgColor: isLight
? AppColors.lightSurfaceContainerHighest
: AppColors.darkSurfaceContainerHighest,
), ),
), ),
); );
@ -548,19 +568,22 @@ class _FeedTelemetryInspectorModalState
vertical: 14, vertical: 14,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF141A22).withValues(alpha: 0.92), color: (isLight
? AppColors.lightSurface
: AppColors.darkSurfaceContainer)
.withValues(alpha: 0.92),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFF2A3647)), border: Border.all(color: colors.outlineVariant),
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: const [ children: [
Icon( Icon(
Icons.sensors_off_rounded, Icons.sensors_off_rounded,
size: 24, size: 24,
color: Color(0xFF8B98A5), color: secondaryTextColor,
), ),
SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'NO RECORDED TELEMETRY SAMPLES', 'NO RECORDED TELEMETRY SAMPLES',
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -569,17 +592,17 @@ class _FeedTelemetryInspectorModalState
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
letterSpacing: 0.5, letterSpacing: 0.5,
color: Colors.white, color: primaryTextColor,
), ),
), ),
SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'Detailed multi-trace waveforms require recorded ride telemetry points.', 'Detailed multi-trace waveforms require recorded ride telemetry points.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontBody, fontFamily: AppTypography.fontBody,
fontSize: 11, fontSize: 11,
color: Color(0xFF8B98A5), color: secondaryTextColor,
), ),
), ),
], ],
@ -597,6 +620,7 @@ class _FeedTelemetryInspectorModalState
MotorSignalThemeColors colors, MotorSignalThemeColors colors,
Color secondaryTextColor, Color secondaryTextColor,
Color accentColor, Color accentColor,
Color primaryTextColor,
) { ) {
final hasRecorded = item.hasRecordedTelemetry; final hasRecorded = item.hasRecordedTelemetry;
final scrubDist = (item.distanceMiles * _scrubFraction).toStringAsFixed(1); final scrubDist = (item.distanceMiles * _scrubFraction).toStringAsFixed(1);
@ -608,9 +632,9 @@ class _FeedTelemetryInspectorModalState
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardSecondary, color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: colors.surfaceBorder), border: Border.all(color: colors.outlineVariant),
), ),
child: Column( child: Column(
children: [ children: [
@ -660,11 +684,11 @@ class _FeedTelemetryInspectorModalState
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
_buildScrubStat('SPEED', hasRecorded ? '$speedVal MPH' : '-- MPH', colors, secondaryTextColor), _buildScrubStat('SPEED', hasRecorded ? '$speedVal MPH' : '-- MPH', colors, secondaryTextColor, primaryTextColor),
Container(width: 1, height: 24, color: colors.surfaceBorder), Container(width: 1, height: 24, color: colors.outlineVariant),
_buildScrubStat('LEAN', hasRecorded ? '$leanVal°' : '--°', colors, secondaryTextColor), _buildScrubStat('LEAN', hasRecorded ? '$leanVal°' : '--°', colors, secondaryTextColor, primaryTextColor),
Container(width: 1, height: 24, color: colors.surfaceBorder), Container(width: 1, height: 24, color: colors.outlineVariant),
_buildScrubStat('LAT G', hasRecorded ? '${gVal}G' : '--G', colors, secondaryTextColor), _buildScrubStat('LAT G', hasRecorded ? '${gVal}G' : '--G', colors, secondaryTextColor, primaryTextColor),
], ],
), ),
], ],
@ -709,6 +733,7 @@ class _FeedTelemetryInspectorModalState
String value, String value,
MotorSignalThemeColors colors, MotorSignalThemeColors colors,
Color secondaryTextColor, Color secondaryTextColor,
Color primaryTextColor,
) { ) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
@ -728,7 +753,7 @@ class _FeedTelemetryInspectorModalState
fontFamily: AppTypography.fontTelemetry, fontFamily: AppTypography.fontTelemetry,
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
], ],
@ -739,12 +764,13 @@ class _FeedTelemetryInspectorModalState
BuildContext context, BuildContext context,
MotorSignalThemeColors colors, MotorSignalThemeColors colors,
Color accentColor, Color accentColor,
Color primaryTextColor,
) { ) {
return SizedBox( return SizedBox(
height: 48, height: 48,
child: OutlinedButton.icon( child: OutlinedButton.icon(
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
backgroundColor: colors.surfaceCardSecondary, backgroundColor: colors.surfaceContainerHigh,
side: BorderSide( side: BorderSide(
color: accentColor.withValues(alpha: 0.5), color: accentColor.withValues(alpha: 0.5),
width: 1.5, width: 1.5,
@ -765,7 +791,7 @@ class _FeedTelemetryInspectorModalState
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
letterSpacing: 0.8, letterSpacing: 0.8,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
onPressed: () { onPressed: () {
@ -783,11 +809,19 @@ class _TelemetryWaveformPainter extends CustomPainter {
final _InspectorTraceTab tab; final _InspectorTraceTab tab;
final SocialFeedItem item; final SocialFeedItem item;
final double scrubFraction; final double scrubFraction;
final Color accentColor;
final Color gridColor;
final Color baselineColor;
final Color badgeBgColor;
_TelemetryWaveformPainter({ _TelemetryWaveformPainter({
required this.tab, required this.tab,
required this.item, required this.item,
required this.scrubFraction, required this.scrubFraction,
required this.accentColor,
required this.gridColor,
required this.baselineColor,
required this.badgeBgColor,
}); });
@override @override
@ -797,7 +831,7 @@ class _TelemetryWaveformPainter extends CustomPainter {
// Draw subtle grid lines // Draw subtle grid lines
final gridPaint = Paint() final gridPaint = Paint()
..color = const Color(0xFF19222E) ..color = gridColor
..strokeWidth = 1.0; ..strokeWidth = 1.0;
for (var i = 1; i <= 4; i++) { for (var i = 1; i <= 4; i++) {
@ -828,7 +862,7 @@ class _TelemetryWaveformPainter extends CustomPainter {
// Draw Scrub Cursor Line & Indicator // Draw Scrub Cursor Line & Indicator
final scrubX = (w * scrubFraction).clamp(0.0, w); final scrubX = (w * scrubFraction).clamp(0.0, w);
final scrubPaint = Paint() final scrubPaint = Paint()
..color = AppColors.electricCyan ..color = accentColor
..strokeWidth = 1.8; ..strokeWidth = 1.8;
canvas.drawLine(Offset(scrubX, 0), Offset(scrubX, h), scrubPaint); canvas.drawLine(Offset(scrubX, 0), Offset(scrubX, h), scrubPaint);
@ -847,7 +881,7 @@ class _TelemetryWaveformPainter extends CustomPainter {
// Baseline (0° upright) // Baseline (0° upright)
final baselinePaint = Paint() final baselinePaint = Paint()
..color = const Color(0xFF2A3647) ..color = baselineColor
..strokeWidth = 1.2 ..strokeWidth = 1.2
..style = PaintingStyle.stroke; ..style = PaintingStyle.stroke;
canvas.drawLine(Offset(0, midY), Offset(w, midY), baselinePaint); canvas.drawLine(Offset(0, midY), Offset(w, midY), baselinePaint);
@ -886,25 +920,25 @@ class _TelemetryWaveformPainter extends CustomPainter {
..close(); ..close();
final fillPaint = Paint() final fillPaint = Paint()
..shader = const LinearGradient( ..shader = LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [ colors: [
Color(0x3300F2FE), accentColor.withValues(alpha: 0.20),
Color(0x0500F2FE), accentColor.withValues(alpha: 0.02),
], ],
).createShader(Rect.fromLTWH(0, 0, w, h)); ).createShader(Rect.fromLTWH(0, 0, w, h));
canvas.drawPath(fillPath, fillPaint); canvas.drawPath(fillPath, fillPaint);
// Draw Trace Line // Draw Trace Line
final linePaint = Paint() final linePaint = Paint()
..color = AppColors.electricCyan ..color = accentColor
..strokeWidth = 2.5 ..strokeWidth = 2.5
..style = PaintingStyle.stroke; ..style = PaintingStyle.stroke;
canvas.drawPath(path, linePaint); canvas.drawPath(path, linePaint);
if (peakIndex >= 0 && peakAbsLean > 0) { if (peakIndex >= 0 && peakAbsLean > 0) {
const markerColor = Color(0xFF00E676); final markerColor = AppColors.semanticSuccess;
final dotPaint = Paint()..color = markerColor; final dotPaint = Paint()..color = markerColor;
canvas.drawCircle(peakPoint, 5.0, dotPaint); canvas.drawCircle(peakPoint, 5.0, dotPaint);
@ -1079,7 +1113,7 @@ class _TelemetryWaveformPainter extends CustomPainter {
Rect.fromLTWH(offset.dx - 4, offset.dy - 2, painter.width + 8, painter.height + 4), Rect.fromLTWH(offset.dx - 4, offset.dy - 2, painter.width + 8, painter.height + 4),
const Radius.circular(4), const Radius.circular(4),
); );
final bgPaint = Paint()..color = const Color(0xFF0F151D); final bgPaint = Paint()..color = badgeBgColor;
final borderPaint = Paint() final borderPaint = Paint()
..color = color.withValues(alpha: 0.5) ..color = color.withValues(alpha: 0.5)
..style = PaintingStyle.stroke ..style = PaintingStyle.stroke
@ -1094,6 +1128,10 @@ class _TelemetryWaveformPainter extends CustomPainter {
bool shouldRepaint(covariant _TelemetryWaveformPainter oldDelegate) { bool shouldRepaint(covariant _TelemetryWaveformPainter oldDelegate) {
return oldDelegate.tab != tab || return oldDelegate.tab != tab ||
oldDelegate.scrubFraction != scrubFraction || oldDelegate.scrubFraction != scrubFraction ||
oldDelegate.item != item; oldDelegate.item != item ||
oldDelegate.accentColor != accentColor ||
oldDelegate.gridColor != gridColor ||
oldDelegate.baselineColor != baselineColor ||
oldDelegate.badgeBgColor != badgeBgColor;
} }
} }

View File

@ -2,8 +2,9 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../../../../core/theme/app_colors.dart'; import '../../../../core/theme/app_colors.dart';
import '../../../../core/theme/app_typography.dart'; import '../../../../core/theme/app_typography.dart';
import '../../../../core/theme/theme_extensions.dart';
/// Glove-friendly quick launcher button for the Squad & Canyon Community Feed. /// Glove-friendly quick launcher button for the Squad Community Feed.
/// ///
/// Designed to sit directly in the Dashboard status bar for instantaneous /// Designed to sit directly in the Dashboard status bar for instantaneous
/// 1-tap access to live rider activity, route snapshots, and kudos. /// 1-tap access to live rider activity, route snapshots, and kudos.
@ -14,15 +15,20 @@ class QuickSquadFeedButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colors = context.motorColors;
final isLight = Theme.of(context).brightness == Brightness.light;
final accentColor = isLight ? AppColors.brandBlueStrong : AppColors.brandBlue;
final textColor = isLight ? AppColors.lightOnSurface : AppColors.darkOnSurface;
if (compact) { if (compact) {
return IconButton( return IconButton(
key: const ValueKey('dash_quick_feed_button'), key: const ValueKey('dash_quick_feed_button'),
icon: const Icon( icon: Icon(
Icons.dynamic_feed_rounded, Icons.dynamic_feed_rounded,
color: AppColors.electricCyan, color: accentColor,
size: 19, size: 19,
), ),
tooltip: 'Squad & Canyon Feed', tooltip: 'Squad Feed',
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 32, minHeight: 32), constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
onPressed: () => context.push('/social/feed'), onPressed: () => context.push('/social/feed'),
@ -36,10 +42,10 @@ class QuickSquadFeedButton extends StatelessWidget {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF181C21), color: colors.surfaceContainer,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all( border: Border.all(
color: AppColors.electricCyan.withValues(alpha: 0.3), color: colors.outlineVariant,
width: 0.8, width: 0.8,
), ),
), ),
@ -49,26 +55,26 @@ class QuickSquadFeedButton extends StatelessWidget {
Container( Container(
width: 7, width: 7,
height: 7, height: 7,
decoration: const BoxDecoration( decoration: BoxDecoration(
color: AppColors.electricCyan, color: accentColor,
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.electricCyan, color: accentColor.withValues(alpha: 0.4),
blurRadius: 4, blurRadius: 4,
), ),
], ],
), ),
), ),
const SizedBox(width: 5), const SizedBox(width: 5),
const Text( Text(
'FEED', 'FEED',
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontTelemetry, fontFamily: AppTypography.fontTelemetry,
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
letterSpacing: 0.6, letterSpacing: 0.6,
color: Color(0xFFC3F5FF), color: textColor,
), ),
), ),
], ],

View File

@ -341,9 +341,7 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
authorHandle: authorHandle, authorHandle: authorHandle,
authorBadge: 'PRO', authorBadge: 'PRO',
clubName: 'Bay Area Twisties Club', clubName: 'Bay Area Twisties Club',
locationName: _locationController.text.trim().isNotEmpty locationName: _locationController.text.trim(),
? _locationController.text.trim()
: 'Location unavailable',
rideTitle: _titleController.text.trim().isNotEmpty rideTitle: _titleController.text.trim().isNotEmpty
? _titleController.text.trim() ? _titleController.text.trim()
: 'Morning Ride', : 'Morning Ride',
@ -369,22 +367,26 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
ref.read(feedFilterStateProvider.notifier).state = FeedFilter.allActivity; ref.read(feedFilterStateProvider.notifier).state = FeedFilter.allActivity;
if (mounted) { if (mounted) {
final colors = context.motorColors;
final isLight = Theme.of(context).brightness == Brightness.light;
Navigator.of(context).pop(); Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
backgroundColor: const Color(0xFF1D2025), backgroundColor: colors.surfaceContainerHigh,
content: Row( content: Row(
children: const [ children: [
Icon(Icons.check_circle_rounded, const Icon(Icons.check_circle_rounded,
color: AppColors.electricCyan, size: 20), color: AppColors.semanticSuccess, size: 20),
SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
child: Text( child: Text(
'Ride session published to Community Feed!', 'Ride session published to Community Feed!',
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontHeadline, fontFamily: AppTypography.fontHeadline,
fontSize: 13, fontSize: 13,
color: Colors.white, color: isLight
? AppColors.lightOnSurface
: AppColors.darkOnSurface,
), ),
), ),
), ),
@ -406,9 +408,11 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
final isLight = Theme.of(context).brightness == Brightness.light; final isLight = Theme.of(context).brightness == Brightness.light;
final secondaryTextColor = final secondaryTextColor =
isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant;
final primaryTextColor =
isLight ? AppColors.lightOnSurface : AppColors.darkOnSurface;
final accentColor = final accentColor =
isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; isLight ? AppColors.brandBlueStrong : AppColors.brandBlue;
final buttonFg = isLight ? AppColors.onBrand : const Color(0xFF00363D); final buttonFg = AppColors.onBrand;
final bottomInset = MediaQuery.of(context).viewInsets.bottom; final bottomInset = MediaQuery.of(context).viewInsets.bottom;
return ConstrainedBox( return ConstrainedBox(
@ -417,9 +421,9 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
), ),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardPrimary, color: colors.surfaceContainer,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
border: Border.all(color: colors.surfaceBorder), border: Border.all(color: colors.outlineVariant),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: colors.glowShadow, color: colors.glowShadow,
@ -449,7 +453,7 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
width: 44, width: 44,
height: 4, height: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceBorder, color: colors.outlineVariant,
borderRadius: BorderRadius.circular(2), borderRadius: BorderRadius.circular(2),
), ),
), ),
@ -485,7 +489,7 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
fontFamily: AppTypography.fontHeadline, fontFamily: AppTypography.fontHeadline,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: colors.textPrimary, color: primaryTextColor,
), ),
), ),
Text( Text(
@ -527,21 +531,21 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 14), padding: const EdgeInsets.symmetric(horizontal: 14),
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardSecondary, color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all(color: accentColor.withValues(alpha: 0.4)), border: Border.all(color: colors.outlineVariant),
), ),
child: DropdownButtonHideUnderline( child: DropdownButtonHideUnderline(
child: DropdownButton<Ride>( child: DropdownButton<Ride>(
key: const ValueKey('share_session_ride_picker'), key: const ValueKey('share_session_ride_picker'),
value: _selectedRide, value: _selectedRide,
isExpanded: true, isExpanded: true,
dropdownColor: colors.surfaceCardSecondary, dropdownColor: colors.surfaceContainerHigh,
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontHeadline, fontFamily: AppTypography.fontHeadline,
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: colors.textPrimary, color: primaryTextColor,
), ),
items: _recentRides.map((ride) { items: _recentRides.map((ride) {
final dist = ride.distanceMiles > 0 ? ride.distanceMiles : ride.distance; final dist = ride.distanceMiles > 0 ? ride.distanceMiles : ride.distance;
@ -578,7 +582,7 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontBody, fontFamily: AppTypography.fontBody,
fontSize: 14, fontSize: 14,
color: colors.textPrimary, color: primaryTextColor,
), ),
decoration: _buildInputDecoration( decoration: _buildInputDecoration(
hint: 'e.g. Sunday Skyline & Pescadero Carve', hint: 'e.g. Sunday Skyline & Pescadero Carve',
@ -598,7 +602,7 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontBody, fontFamily: AppTypography.fontBody,
fontSize: 14, fontSize: 14,
color: colors.textPrimary, color: primaryTextColor,
), ),
decoration: _buildInputDecoration( decoration: _buildInputDecoration(
hint: 'e.g. 2024 Ducati Streetfighter V4', hint: 'e.g. 2024 Ducati Streetfighter V4',
@ -616,20 +620,20 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 14), padding: const EdgeInsets.symmetric(horizontal: 14),
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardSecondary, color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all(color: colors.surfaceBorder), border: Border.all(color: colors.outlineVariant),
), ),
child: DropdownButtonHideUnderline( child: DropdownButtonHideUnderline(
child: DropdownButton<RidingDiscipline>( child: DropdownButton<RidingDiscipline>(
value: _selectedDiscipline, value: _selectedDiscipline,
isExpanded: true, isExpanded: true,
dropdownColor: colors.surfaceCardSecondary, dropdownColor: colors.surfaceContainerHigh,
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontHeadline, fontFamily: AppTypography.fontHeadline,
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: colors.textPrimary, color: primaryTextColor,
), ),
items: RidingDiscipline.values items: RidingDiscipline.values
.where((d) => d != RidingDiscipline.all) .where((d) => d != RidingDiscipline.all)
@ -655,7 +659,7 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
style: TextStyle( style: TextStyle(
fontFamily: AppTypography.fontBody, fontFamily: AppTypography.fontBody,
fontSize: 14, fontSize: 14,
color: colors.textPrimary, color: primaryTextColor,
), ),
decoration: _buildInputDecoration( decoration: _buildInputDecoration(
hint: 'e.g. Saratoga, CA or Trailhead', hint: 'e.g. Saratoga, CA or Trailhead',
@ -829,6 +833,10 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
required Color accentColor, required Color accentColor,
bool highlight = false, bool highlight = false,
}) { }) {
final primaryTextColor = colors.surfaceContainer == AppColors.darkSurfaceContainer
? AppColors.darkOnSurface
: AppColors.lightOnSurface;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -838,9 +846,9 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 11), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 11),
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceCardSecondary, color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all(color: colors.surfaceBorder), border: Border.all(color: colors.outlineVariant),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -853,7 +861,7 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
fontFamily: AppTypography.fontTelemetry, fontFamily: AppTypography.fontTelemetry,
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: highlight ? accentColor : colors.textPrimary, color: highlight ? accentColor : primaryTextColor,
), ),
), ),
), ),
@ -903,15 +911,15 @@ class _ShareSessionModalState extends ConsumerState<ShareSessionModal> {
color: secondaryTextColor, color: secondaryTextColor,
), ),
filled: true, filled: true,
fillColor: colors.surfaceCardSecondary, fillColor: colors.surfaceContainerHigh,
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colors.surfaceBorder), borderSide: BorderSide(color: colors.outlineVariant),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colors.surfaceBorder), borderSide: BorderSide(color: colors.outlineVariant),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),

View File

@ -251,7 +251,7 @@ class _SocialHubScreenState extends ConsumerState<SocialHubScreen> {
key: const ValueKey('social_card_feed'), key: const ValueKey('social_card_feed'),
icon: Icons.dynamic_feed_outlined, icon: Icons.dynamic_feed_outlined,
title: 'Community Feed', title: 'Community Feed',
subtitle: 'Squad telemetry, route snapshots, canyon paces & fist-bump kudos', subtitle: 'Squad telemetry, route snapshots, paces & kudos',
colors: colors, colors: colors,
onTap: () => setState(() => _currentTab = SocialHubTab.feed), onTap: () => setState(() => _currentTab = SocialHubTab.feed),
), ),
@ -276,7 +276,7 @@ class _SocialHubScreenState extends ConsumerState<SocialHubScreen> {
key: const ValueKey('social_card_feed'), key: const ValueKey('social_card_feed'),
icon: Icons.dynamic_feed_outlined, icon: Icons.dynamic_feed_outlined,
title: 'Community Feed', title: 'Community Feed',
subtitle: 'Squad telemetry, route snapshots, canyon paces & fist-bump kudos', subtitle: 'Squad telemetry, route snapshots, paces & kudos',
colors: colors, colors: colors,
onTap: () => setState(() => _currentTab = SocialHubTab.feed), onTap: () => setState(() => _currentTab = SocialHubTab.feed),
), ),

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../map/map_provider_interface.dart'; import '../map/map_provider_interface.dart';
import 'route_models.dart'; import 'route_models.dart';
@ -41,6 +42,31 @@ class AwsLocationRouteProvider implements RouteProvider {
@override @override
String get name => 'Amazon AWS Location'; String get name => 'Amazon AWS Location';
static String? _cleanAddressValue(dynamic value) {
final candidates = value is Map ? [value['Name'], value['Code']] : [value];
for (final candidate in candidates) {
final cleaned = candidate?.toString().trim();
if (cleaned != null && cleaned.isNotEmpty) return cleaned;
}
return null;
}
@visibleForTesting
static String? buildCoarseLocationLabel(Map<String, dynamic> address) {
final locality = _cleanAddressValue(
address['Locality'] ?? address['Municipality'],
);
final region = _cleanAddressValue(address['Region']);
final subRegion = _cleanAddressValue(address['SubRegion']);
final country = _cleanAddressValue(address['Country']);
final adminArea = region ?? subRegion ?? country;
if (locality != null && adminArea != null) {
return '$locality, $adminArea';
}
return locality ?? adminArea;
}
@override @override
Future<List<PlaceSearchResult>> searchPlaces( Future<List<PlaceSearchResult>> searchPlaces(
String query, { String query, {
@ -287,27 +313,8 @@ class AwsLocationRouteProvider implements RouteProvider {
final address = first['Address'] as Map<String, dynamic>?; final address = first['Address'] as Map<String, dynamic>?;
if (address == null) return null; if (address == null) return null;
// Extract coarse locality and region/state — strictly avoid street addresses or exact house numbers // Extract only coarse locality and administrative area.
final locality = address['Locality']?.toString().trim(); return buildCoarseLocationLabel(address);
final regionStr = address['Region']?.toString().trim();
final subRegion = address['SubRegion']?.toString().trim();
if (locality != null &&
locality.isNotEmpty &&
regionStr != null &&
regionStr.isNotEmpty) {
return '$locality, $regionStr';
} else if (locality != null && locality.isNotEmpty) {
return locality;
} else if (subRegion != null &&
subRegion.isNotEmpty &&
regionStr != null &&
regionStr.isNotEmpty) {
return '$subRegion, $regionStr';
} else if (regionStr != null && regionStr.isNotEmpty) {
return regionStr;
}
return null;
} catch (_) { } catch (_) {
return null; return null;
} }

View File

@ -609,7 +609,7 @@ class SocialFeedItem {
authorAvatarPreset: json['authorAvatarPreset'] as String?, authorAvatarPreset: json['authorAvatarPreset'] as String?,
authorBadge: json['authorBadge'] as String?, authorBadge: json['authorBadge'] as String?,
clubName: json['clubName'] as String?, clubName: json['clubName'] as String?,
locationName: json['locationName'] as String? ?? 'Canyon Route', locationName: (json['locationName'] as String? ?? '').trim(),
timestamp: _parseDateTime(json['timestamp']) ?? DateTime.now(), timestamp: _parseDateTime(json['timestamp']) ?? DateTime.now(),
rideTitle: json['rideTitle'] as String? ?? 'Afternoon Carve', rideTitle: json['rideTitle'] as String? ?? 'Afternoon Carve',
bikeModel: json['bikeModel'] as String? ?? 'Motorcycle', bikeModel: json['bikeModel'] as String? ?? 'Motorcycle',

View File

@ -9,6 +9,29 @@ import 'package:motorcycle_dash/services/routes/route_provider.dart';
void main() { void main() {
group('AwsLocationRouteProvider Tests', () { group('AwsLocationRouteProvider Tests', () {
test('builds privacy-safe coarse labels from AWS address value shapes', () {
final mapRegion = AwsLocationRouteProvider.buildCoarseLocationLabel({
'Locality': 'Rome',
'Region': {'Code': 'GA', 'Name': 'Georgia'},
});
final stringRegion = AwsLocationRouteProvider.buildCoarseLocationLabel({
'Locality': 'Rome',
'Region': ' Georgia ',
});
final missingRegion = AwsLocationRouteProvider.buildCoarseLocationLabel({
'Locality': 'Rome',
'Region': {'Name': ' '},
});
expect(mapRegion, 'Rome, Georgia');
expect(stringRegion, 'Rome, Georgia');
expect(missingRegion, 'Rome');
for (final label in [mapRegion, stringRegion, missingRegion]) {
expect(label, isNot(contains('{')));
expect(label, isNot(matches(RegExp(r'\d+\.\d+,\s*-?\d+\.\d+'))));
}
});
test('instance properties and custom configuration', () { test('instance properties and custom configuration', () {
final provider = AwsLocationRouteProvider( final provider = AwsLocationRouteProvider(
apiKey: 'test-key', apiKey: 'test-key',

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:motorcycle_dash/core/theme/app_colors.dart';
import 'package:motorcycle_dash/core/theme/theme_extensions.dart'; import 'package:motorcycle_dash/core/theme/theme_extensions.dart';
import 'package:motorcycle_dash/database/database.dart'; import 'package:motorcycle_dash/database/database.dart';
import 'package:motorcycle_dash/features/social/feed/social_feed_screen.dart'; import 'package:motorcycle_dash/features/social/feed/social_feed_screen.dart';
@ -13,15 +14,21 @@ import 'package:motorcycle_dash/services/social/social_feed_models.dart';
import 'package:motorcycle_dash/services/social/social_feed_providers.dart'; import 'package:motorcycle_dash/services/social/social_feed_providers.dart';
import 'package:motorcycle_dash/services/social/social_feed_service.dart'; import 'package:motorcycle_dash/services/social/social_feed_service.dart';
Widget _createTestApp({required Widget child, List<Override> overrides = const []}) { Widget _createTestApp({
required Widget child,
List<Override> overrides = const [],
ThemeData? theme,
}) {
return ProviderScope( return ProviderScope(
overrides: overrides, overrides: overrides,
child: MaterialApp( child: MaterialApp(
theme: ThemeData.dark().copyWith( theme: theme ??
extensions: const [ ThemeData.dark().copyWith(
MotorSignalThemeColors.dark, colorScheme: const ColorScheme.dark(primary: AppColors.brandBlue),
], extensions: const [
), MotorSignalThemeColors.dark,
],
),
home: Scaffold(body: child), home: Scaffold(body: child),
), ),
); );
@ -105,6 +112,16 @@ void main() {
expect(json['distanceMiles'], 68.4); expect(json['distanceMiles'], 68.4);
expect(json['maxLeanAngleDeg'], 51.0); expect(json['maxLeanAngleDeg'], 51.0);
}); });
test('SocialFeedItem.fromJson preserves a missing location as empty', () {
final item = SocialFeedItem.fromJson({
'id': 'feed_missing_location',
'authorUid': 'uid_1',
'timestamp': '2026-10-17T12:00:00.000Z',
});
expect(item.locationName, isEmpty);
});
}); });
group('SocialFeedService Unit Tests', () { group('SocialFeedService Unit Tests', () {
@ -196,6 +213,41 @@ void main() {
}); });
group('SocialFeedScreen & Sub-Widgets Widget Tests', () { group('SocialFeedScreen & Sub-Widgets Widget Tests', () {
testWidgets('Renders feed timestamps without fabricated empty locations', (tester) async {
await tester.binding.setSurfaceSize(const Size(400, 1000));
final service = SocialFeedService();
await service.shareRecentRide(
authorUid: 'uid_known_location',
authorDisplayName: 'Known Location Rider',
authorHandle: 'known_location',
locationName: 'Known Place',
rideTitle: 'Known Location Ride',
bikeModel: 'Test Bike',
);
await service.shareRecentRide(
authorUid: 'uid_empty_location',
authorDisplayName: 'Empty Location Rider',
authorHandle: 'empty_location',
locationName: '',
rideTitle: 'Empty Location Ride',
bikeModel: 'Test Bike',
);
await tester.pumpWidget(
_createTestApp(
overrides: [
socialFeedServiceProvider.overrideWithValue(service),
],
child: const SocialFeedScreen(),
),
);
await tester.pumpAndSettle();
expect(find.text('Just now'), findsOneWidget);
expect(find.text('Just now • Known Place'), findsOneWidget);
expect(find.textContaining('Location unavailable'), findsNothing);
});
testWidgets('Renders SocialFeedScreen with header pills and tabs', (tester) async { testWidgets('Renders SocialFeedScreen with header pills and tabs', (tester) async {
await tester.binding.setSurfaceSize(const Size(400, 800)); await tester.binding.setSurfaceSize(const Size(400, 800));
@ -208,7 +260,7 @@ void main() {
await tester.pump(const Duration(milliseconds: 50)); await tester.pump(const Duration(milliseconds: 50));
expect(find.text('Community Feed'), findsOneWidget); expect(find.text('Community Feed'), findsOneWidget);
expect(find.text('SQUAD & CANYON FEED'), findsOneWidget); expect(find.text('SQUAD FEED'), findsOneWidget);
expect(find.byKey(const ValueKey('feed_search_toggle_button')), findsOneWidget); expect(find.byKey(const ValueKey('feed_search_toggle_button')), findsOneWidget);
expect(find.text('All Activity'), findsOneWidget); expect(find.text('All Activity'), findsOneWidget);
expect(find.text('Club Only'), findsOneWidget); expect(find.text('Club Only'), findsOneWidget);
@ -898,8 +950,8 @@ void main() {
find.text('All Activity'), find.text('All Activity'),
); );
final selectedFilterBg = isLight final selectedFilterBg = isLight
? const Color(0xFF0047B3) ? AppColors.brandBlueStrong
: const Color(0xFF00E5FF); : AppColors.brandBlue;
expect( expect(
contrastRatio(selectedFilterText.style!.color!, selectedFilterBg), contrastRatio(selectedFilterText.style!.color!, selectedFilterBg),
greaterThanOrEqualTo(4.5), greaterThanOrEqualTo(4.5),
@ -1473,4 +1525,199 @@ void main() {
}, },
); );
}); });
group('Issue #49 UI Color & Theme Alignment Tests', () {
final darkTheme = ThemeData.dark().copyWith(
colorScheme: const ColorScheme.dark(primary: AppColors.brandBlue),
extensions: const [MotorSignalThemeColors.dark],
);
final lightTheme = ThemeData.light().copyWith(
colorScheme: const ColorScheme.light(primary: AppColors.brandBlueStrong),
extensions: const [MotorSignalThemeColors.light],
);
testWidgets('ShareSessionModal primary publish button uses brand primary color in dark mode', (tester) async {
await tester.binding.setSurfaceSize(const Size(400, 900));
final ride = Ride(
id: 1,
startTime: DateTime.now().subtract(const Duration(minutes: 30)),
endTime: DateTime.now(),
distance: 12.5,
distanceMiles: 12.5,
movingTime: 1800,
stoppedTime: 0,
topSpeed: 55.0,
topSpeedMph: 55.0,
averageSpeed: 38.0,
maxLeanLeft: 28.0,
maxLeanRight: 25.0,
maxAccelG: 0.45,
maxBrakingG: 0.52,
elevationGain: 320.0,
elevationLoss: 110.0,
);
await tester.pumpWidget(
_createTestApp(
theme: darkTheme,
child: Builder(
builder: (context) => ElevatedButton(
onPressed: () => ShareSessionModal.show(context, initialRide: ride),
child: const Text('Open Modal Dark'),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Open Modal Dark'));
await tester.pumpAndSettle();
final publishBtnFinder = find.widgetWithText(ElevatedButton, 'PUBLISH TO COMMUNITY FEED');
expect(publishBtnFinder, findsOneWidget);
final darkPublishBtn = tester.widget<ElevatedButton>(publishBtnFinder);
expect(darkPublishBtn.style?.backgroundColor?.resolve({}), AppColors.brandBlue);
expect(darkPublishBtn.style?.foregroundColor?.resolve({}), AppColors.onBrand);
// Check rendered text colors in dark mode modal
for (final text in tester.widgetList<Text>(find.byType(Text))) {
final color = text.style?.color;
if (color != null) {
expect(color == const Color(0xFF00272E), isFalse);
expect(color == const Color(0xFF00363D), isFalse);
expect(color == const Color(0xFFBAC9CC), isFalse);
expect(color == const Color(0xFFC3F5FF), isFalse);
expect(color == AppColors.electricCyan, isFalse);
}
}
});
testWidgets('ShareSessionModal primary publish button uses brand primary color in light mode', (tester) async {
await tester.binding.setSurfaceSize(const Size(400, 900));
final ride = Ride(
id: 2,
startTime: DateTime.now().subtract(const Duration(minutes: 30)),
endTime: DateTime.now(),
distance: 12.5,
distanceMiles: 12.5,
movingTime: 1800,
stoppedTime: 0,
topSpeed: 55.0,
topSpeedMph: 55.0,
averageSpeed: 38.0,
maxLeanLeft: 28.0,
maxLeanRight: 25.0,
maxAccelG: 0.45,
maxBrakingG: 0.52,
elevationGain: 320.0,
elevationLoss: 110.0,
);
await tester.pumpWidget(
_createTestApp(
theme: lightTheme,
child: Builder(
builder: (context) => ElevatedButton(
onPressed: () => ShareSessionModal.show(context, initialRide: ride),
child: const Text('Open Modal Light'),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Open Modal Light'));
await tester.pumpAndSettle();
final lightPublishBtnFinder = find.widgetWithText(ElevatedButton, 'PUBLISH TO COMMUNITY FEED');
expect(lightPublishBtnFinder, findsOneWidget);
final lightPublishBtn = tester.widget<ElevatedButton>(lightPublishBtnFinder);
expect(lightPublishBtn.style?.backgroundColor?.resolve({}), AppColors.brandBlueStrong);
expect(lightPublishBtn.style?.foregroundColor?.resolve({}), AppColors.onBrand);
// Check rendered text colors in light mode modal
for (final text in tester.widgetList<Text>(find.byType(Text))) {
final color = text.style?.color;
if (color != null) {
expect(color == const Color(0xFF00272E), isFalse);
expect(color == const Color(0xFF00363D), isFalse);
expect(color == const Color(0xFFBAC9CC), isFalse);
expect(color == const Color(0xFFC3F5FF), isFalse);
expect(color == AppColors.electricCyan, isFalse);
}
}
});
testWidgets('SocialFeedScreen header and sticky button use brand primary and theme colors in dark and light mode', (tester) async {
await tester.binding.setSurfaceSize(const Size(400, 900));
// Dark theme test
await tester.pumpWidget(
_createTestApp(
theme: darkTheme,
child: const SocialFeedScreen(),
),
);
await tester.pumpAndSettle();
final darkShareBtnFinder = find.byKey(const ValueKey('feed_share_session_button'));
expect(darkShareBtnFinder, findsOneWidget);
final darkShareBtn = tester.widget<ElevatedButton>(darkShareBtnFinder);
expect(darkShareBtn.style?.foregroundColor?.resolve({}), AppColors.onBrand);
// Verify selected filter tab uses brandBlue in dark theme
final allActivityText = find.text('All Activity');
expect(allActivityText, findsOneWidget);
final animatedContainers = tester.widgetList<AnimatedContainer>(find.byType(AnimatedContainer));
final selectedTabContainer = animatedContainers.firstWhere((c) {
final box = c.decoration as BoxDecoration?;
return box?.color == AppColors.brandBlue;
});
expect(selectedTabContainer, isNotNull);
// Verify no rendered text widget uses old cyan/navy literals
for (final text in tester.widgetList<Text>(find.byType(Text))) {
final color = text.style?.color;
if (color != null) {
expect(color == const Color(0xFF00272E), isFalse);
expect(color == const Color(0xFF00363D), isFalse);
expect(color == const Color(0xFFBAC9CC), isFalse);
expect(color == const Color(0xFFC3F5FF), isFalse);
expect(color == AppColors.electricCyan, isFalse);
}
}
// Light theme test
await tester.pumpWidget(
_createTestApp(
theme: lightTheme,
child: const SocialFeedScreen(),
),
);
await tester.pumpAndSettle();
final lightShareBtnFinder = find.byKey(const ValueKey('feed_share_session_button'));
expect(lightShareBtnFinder, findsOneWidget);
final lightShareBtn = tester.widget<ElevatedButton>(lightShareBtnFinder);
expect(lightShareBtn.style?.foregroundColor?.resolve({}), AppColors.onBrand);
// Verify selected filter tab uses brandBlueStrong in light theme
final lightAnimatedContainers = tester.widgetList<AnimatedContainer>(find.byType(AnimatedContainer));
final lightSelectedTabContainer = lightAnimatedContainers.firstWhere((c) {
final box = c.decoration as BoxDecoration?;
return box?.color == AppColors.brandBlueStrong;
});
expect(lightSelectedTabContainer, isNotNull);
// Verify no rendered text widget uses old cyan/navy literals
for (final text in tester.widgetList<Text>(find.byType(Text))) {
final color = text.style?.color;
if (color != null) {
expect(color == const Color(0xFF00272E), isFalse);
expect(color == const Color(0xFF00363D), isFalse);
expect(color == const Color(0xFFBAC9CC), isFalse);
expect(color == const Color(0xFFC3F5FF), isFalse);
expect(color == AppColors.electricCyan, isFalse);
}
}
});
});
} }

View File

@ -47,7 +47,7 @@ void main() {
// 3. We land directly on Community Feed! // 3. We land directly on Community Feed!
expect(find.byType(SocialFeedScreen), findsOneWidget); expect(find.byType(SocialFeedScreen), findsOneWidget);
expect(find.text('SQUAD & CANYON FEED'), findsOneWidget); expect(find.text('SQUAD FEED'), findsOneWidget);
// 4. Tap 'Hub' toggle in AppBar to view Rider Hub // 4. Tap 'Hub' toggle in AppBar to view Rider Hub
final hubToggle = find.byKey(const ValueKey('social_tab_hub')); final hubToggle = find.byKey(const ValueKey('social_tab_hub'));
@ -63,6 +63,6 @@ void main() {
await tester.tap(feedToggle); await tester.tap(feedToggle);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.text('SQUAD & CANYON FEED'), findsOneWidget); expect(find.text('SQUAD FEED'), findsOneWidget);
}); });
} }

View File

@ -78,7 +78,7 @@ void main() {
// Verify header and filter components render cleanly // Verify header and filter components render cleanly
expect(find.text('Community Feed'), findsOneWidget); expect(find.text('Community Feed'), findsOneWidget);
expect(find.text('SQUAD & CANYON FEED'), findsOneWidget); expect(find.text('SQUAD FEED'), findsOneWidget);
expect(find.byKey(const ValueKey('feed_search_toggle_button')), findsOneWidget); expect(find.byKey(const ValueKey('feed_search_toggle_button')), findsOneWidget);
expect(find.text('SHARE RECENT SESSION'), findsOneWidget); expect(find.text('SHARE RECENT SESSION'), findsOneWidget);