diff --git a/lib/features/social/feed/social_feed_screen.dart b/lib/features/social/feed/social_feed_screen.dart index 180c158..f5edee6 100644 --- a/lib/features/social/feed/social_feed_screen.dart +++ b/lib/features/social/feed/social_feed_screen.dart @@ -121,25 +121,29 @@ class _SocialFeedScreenState extends ConsumerState { ); 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 : colors.brandCyanBright; + isLight ? AppColors.brandBlueStrong : AppColors.brandBlue; + final backgroundColor = + isLight ? AppColors.lightBackground : AppColors.darkBackground; if (!widget.showAppBar) { return Container( - color: colors.surfaceCanvas, + color: backgroundColor, child: content, ); } return Scaffold( - backgroundColor: colors.surfaceCanvas, + backgroundColor: backgroundColor, appBar: AppBar( - backgroundColor: colors.surfaceCanvas, + backgroundColor: backgroundColor, elevation: 0, leading: IconButton( - icon: Icon(Icons.arrow_back_rounded, color: colors.textPrimary), + icon: Icon(Icons.arrow_back_rounded, color: primaryTextColor), onPressed: () { if (context.canPop()) { context.pop(); @@ -176,11 +180,11 @@ class _SocialFeedScreenState extends ConsumerState { fontSize: 16, fontWeight: FontWeight.w700, letterSpacing: 0.2, - color: colors.textPrimary, + color: primaryTextColor, ), ), Text( - 'SQUAD & CANYON TELEMETRY', + 'SQUAD TELEMETRY', overflow: TextOverflow.ellipsis, style: TextStyle( fontFamily: AppTypography.fontTelemetry, @@ -235,14 +239,16 @@ class _SocialFeedScreenState extends ConsumerState { MotorSignalThemeColors colors, ) { 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 : colors.brandCyanBright; + isLight ? AppColors.brandBlueStrong : AppColors.brandBlue; return Container( padding: const EdgeInsets.fromLTRB(16, 10, 16, 8), - color: colors.surfaceCanvas, + color: Colors.transparent, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -256,7 +262,7 @@ class _SocialFeedScreenState extends ConsumerState { child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( - color: colors.surfaceCardSecondary, + color: colors.surfaceContainer, borderRadius: BorderRadius.circular(20), border: Border.all( color: accentColor.withValues(alpha: isLight ? 0.5 : 0.3), @@ -276,7 +282,7 @@ class _SocialFeedScreenState extends ConsumerState { const SizedBox(width: 8), Flexible( child: Text( - 'SQUAD & CANYON FEED', + 'SQUAD FEED', maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( @@ -321,10 +327,10 @@ class _SocialFeedScreenState extends ConsumerState { Container( height: 42, decoration: BoxDecoration( - color: colors.surfaceCardPrimary, + color: colors.surfaceContainer, borderRadius: BorderRadius.circular(10), border: Border.all( - color: accentColor.withValues(alpha: 0.4), + color: colors.outlineVariant, ), ), child: TextField( @@ -333,7 +339,7 @@ class _SocialFeedScreenState extends ConsumerState { style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 13, - color: colors.textPrimary, + color: primaryTextColor, ), decoration: InputDecoration( hintText: 'Search rider, route, bike, or region...', @@ -409,7 +415,7 @@ class _SocialFeedScreenState extends ConsumerState { Container( width: 1, height: 20, - color: colors.surfaceBorder, + color: colors.outlineVariant, ), const SizedBox(width: 8), _buildDisciplineChip( @@ -420,7 +426,7 @@ class _SocialFeedScreenState extends ConsumerState { const SizedBox(width: 6), _buildDisciplineChip( discipline: RidingDiscipline.canyonSport, - label: '⚡ Canyon', + label: '⚡ Sport', isSelected: activeDiscipline == RidingDiscipline.canyonSport, ), const SizedBox(width: 6), @@ -506,8 +512,8 @@ class _SocialFeedScreenState extends ConsumerState { }) { final colors = context.motorColors; final isLight = Theme.of(context).brightness == Brightness.light; - final selectedBg = isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; - final selectedFg = isLight ? AppColors.onBrand : const Color(0xFF00272E); + final selectedBg = isLight ? AppColors.brandBlueStrong : AppColors.brandBlue; + final selectedFg = AppColors.onBrand; final unselectedFg = isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; @@ -523,10 +529,10 @@ class _SocialFeedScreenState extends ConsumerState { padding: const EdgeInsets.symmetric(horizontal: 10), alignment: Alignment.center, decoration: BoxDecoration( - color: isSelected ? selectedBg : colors.surfaceCardSecondary, + color: isSelected ? selectedBg : colors.surfaceContainer, borderRadius: BorderRadius.circular(10), border: Border.all( - color: isSelected ? selectedBg : colors.surfaceBorder, + color: isSelected ? selectedBg : colors.outlineVariant, ), ), child: Text( @@ -550,8 +556,8 @@ class _SocialFeedScreenState extends ConsumerState { }) { final colors = context.motorColors; final isLight = Theme.of(context).brightness == Brightness.light; - final selectedBg = isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; - final selectedFg = isLight ? AppColors.onBrand : const Color(0xFF00272E); + final selectedBg = isLight ? AppColors.brandBlueStrong : AppColors.brandBlue; + final selectedFg = AppColors.onBrand; final unselectedFg = isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; @@ -566,10 +572,10 @@ class _SocialFeedScreenState extends ConsumerState { padding: const EdgeInsets.symmetric(horizontal: 10), alignment: Alignment.center, decoration: BoxDecoration( - color: isSelected ? selectedBg : colors.surfaceCardSecondary, + color: isSelected ? selectedBg : colors.surfaceContainer, borderRadius: BorderRadius.circular(8), border: Border.all( - color: isSelected ? selectedBg : colors.surfaceBorder, + color: isSelected ? selectedBg : colors.outlineVariant, width: isSelected ? 1.2 : 1.0, ), ), @@ -596,10 +602,12 @@ class _SocialFeedScreenState extends ConsumerState { final hasCustomFilters = activeDiscipline != RidingDiscipline.all || searchQuery.isNotEmpty; 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 : colors.brandCyanBright; + isLight ? AppColors.brandBlueStrong : AppColors.brandBlue; return LayoutBuilder( builder: (context, constraints) { @@ -632,7 +640,7 @@ class _SocialFeedScreenState extends ConsumerState { fontFamily: AppTypography.fontHeadline, fontSize: 16, fontWeight: FontWeight.w700, - color: colors.textPrimary, + color: primaryTextColor, ), ), const SizedBox(height: 6), @@ -725,8 +733,8 @@ class _SocialFeedScreenState extends ConsumerState { Widget _buildStickyShareButton(BuildContext context) { final isLight = Theme.of(context).brightness == Brightness.light; - final buttonBg = isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; - final buttonFg = isLight ? AppColors.onBrand : const Color(0xFF00272E); + final buttonBg = isLight ? AppColors.brandBlueStrong : AppColors.brandBlue; + final buttonFg = AppColors.onBrand; return Container( height: 52, @@ -800,13 +808,13 @@ Color _getDisciplineColor(RidingDiscipline discipline, {bool isLight = false}) { case RidingDiscipline.advDualSport: return AppColors.telemetryGreen; case RidingDiscipline.touring: - return AppColors.electricCyan; + return AppColors.brandBlue; case RidingDiscipline.trackDay: return AppColors.semanticDangerDark; case RidingDiscipline.cruiser: - return AppColors.electricCyan; + return AppColors.brandBlue; case RidingDiscipline.all: - return AppColors.electricCyan; + return AppColors.brandBlue; } } @@ -905,11 +913,14 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> Color accentColor, SocialFeedItem item, ) { + final isLight = Theme.of(context).brightness == Brightness.light; + final primaryTextColor = + isLight ? AppColors.lightOnSurface : AppColors.darkOnSurface; final isLandscape = MediaQuery.of(context).orientation == Orientation.landscape; ScaffoldMessenger.of(context).showSnackBar( SnackBar( - backgroundColor: colors.surfaceCardSecondary, + backgroundColor: colors.surfaceContainerHigh, behavior: SnackBarBehavior.floating, margin: EdgeInsets.fromLTRB( 16, @@ -919,13 +930,13 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), - side: BorderSide(color: colors.surfaceBorder), + side: BorderSide(color: colors.outlineVariant), ), content: Row( children: [ - Icon( + const Icon( Icons.download_done_rounded, - color: accentColor, + color: AppColors.semanticSuccess, ), const SizedBox(width: 10), Expanded( @@ -935,7 +946,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> overflow: TextOverflow.ellipsis, style: TextStyle( fontFamily: AppTypography.fontBody, - color: colors.textPrimary, + color: primaryTextColor, ), ), ), @@ -949,16 +960,18 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> Widget build(BuildContext context) { 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 : colors.brandCyanBright; + isLight ? AppColors.brandBlueStrong : AppColors.brandBlue; final demoColor = isLight ? AppColors.semanticWarningLight : AppColors.hazardAmber; final disciplineColor = _getDisciplineColor(widget.item.discipline, isLight: isLight); - final bumpedBg = isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; - final bumpedFg = isLight ? AppColors.onBrand : const Color(0xFF00272E); + final bumpedBg = isLight ? AppColors.brandBlueStrong : AppColors.brandBlue; + final bumpedFg = AppColors.onBrand; final item = widget.item; final user = ref.watch(currentUserProvider); @@ -968,9 +981,9 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> return Container( decoration: BoxDecoration( - color: colors.surfaceCardPrimary, + color: colors.surfaceContainer, borderRadius: BorderRadius.circular(18), - border: Border.all(color: colors.surfaceBorder), + border: Border.all(color: colors.outlineVariant), boxShadow: [ BoxShadow( color: colors.glowShadow, @@ -993,7 +1006,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> children: [ CircleAvatar( radius: 22, - backgroundColor: colors.surfaceCardSecondary, + backgroundColor: colors.surfaceContainerHigh, child: Icon( Icons.sports_motorsports_rounded, color: accentColor, @@ -1010,7 +1023,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> color: accentColor, shape: BoxShape.circle, border: Border.all( - color: colors.surfaceCardPrimary, + color: colors.surfaceContainer, width: 1.5, ), ), @@ -1036,7 +1049,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> fontFamily: AppTypography.fontHeadline, fontSize: 15, fontWeight: FontWeight.w700, - color: colors.textPrimary, + color: primaryTextColor, ), ), if (item.isDemo) ...[ @@ -1123,7 +1136,9 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> ], const SizedBox(height: 2), Text( - '${_formatRelativeTime(item.timestamp)} • ${item.locationName}', + item.locationName.isEmpty + ? _formatRelativeTime(item.timestamp) + : '${_formatRelativeTime(item.timestamp)} • ${item.locationName}', style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 12.5, @@ -1142,10 +1157,10 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> color: secondaryTextColor, size: 20, ), - color: colors.surfaceCardSecondary, + color: colors.surfaceContainerHigh, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), - side: BorderSide(color: colors.surfaceBorder), + side: BorderSide(color: colors.outlineVariant), ), onSelected: (val) { if (val == 'export_gpx') { @@ -1154,7 +1169,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> } ScaffoldMessenger.of(context).showSnackBar( SnackBar( - backgroundColor: colors.surfaceCardSecondary, + backgroundColor: colors.surfaceContainerHigh, content: Row( children: [ Expanded( @@ -1162,7 +1177,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> 'Action: $val', maxLines: 2, overflow: TextOverflow.ellipsis, - style: TextStyle(color: colors.textPrimary), + style: TextStyle(color: primaryTextColor), ), ), ], @@ -1176,21 +1191,21 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> value: 'copy_link', child: Text( 'Copy Route Link', - style: TextStyle(color: colors.textPrimary, fontSize: 13), + style: TextStyle(color: primaryTextColor, fontSize: 13), ), ), PopupMenuItem( value: 'export_gpx', child: Text( 'Export GPX File', - style: TextStyle(color: colors.textPrimary, fontSize: 13), + style: TextStyle(color: primaryTextColor, fontSize: 13), ), ), PopupMenuItem( value: 'report', child: Text( '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, fontWeight: FontWeight.w700, letterSpacing: -0.3, - color: colors.textPrimary, + color: primaryTextColor, ), ), const SizedBox(height: 6), @@ -1220,9 +1235,9 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: colors.surfaceCardSecondary, + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(6), - border: Border.all(color: colors.surfaceBorder), + border: Border.all(color: colors.outlineVariant), ), child: Row( mainAxisSize: MainAxisSize.min, @@ -1242,7 +1257,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> fontFamily: AppTypography.fontTelemetry, fontSize: 11.5, fontWeight: FontWeight.w600, - color: colors.textPrimary, + color: primaryTextColor, ), ), ), @@ -1280,9 +1295,9 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: colors.surfaceCardSecondary, + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(14), - border: Border.all(color: colors.surfaceBorder), + border: Border.all(color: colors.outlineVariant), ), child: Column( children: [ @@ -1293,7 +1308,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> label: 'DISTANCE', value: item.distanceMiles.toStringAsFixed(1), unit: 'MI', - valueColor: colors.textPrimary, + valueColor: primaryTextColor, ), const SizedBox(width: 6), _buildMetricCell( @@ -1301,7 +1316,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> label: 'DURATION', value: _formatDuration(item.durationSeconds), unit: '', - valueColor: colors.textPrimary, + valueColor: primaryTextColor, ), ], ), @@ -1313,7 +1328,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> label: 'MAX LEAN', value: '${item.maxLeanAngleDeg.toStringAsFixed(0)}°', unit: item.maxLeanSide, - valueColor: accentColor, + valueColor: primaryTextColor, indicatorColor: accentColor, ), const SizedBox(width: 6), @@ -1324,7 +1339,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> unit: item.paceFactor.split(' ').length > 1 ? item.paceFactor.split(' ').sublist(1).join(' ') : '', - valueColor: accentColor, + valueColor: primaryTextColor, ), ], ), @@ -1365,12 +1380,12 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> decoration: BoxDecoration( color: isBumped ? bumpedBg - : colors.surfaceCardSecondary, + : colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), border: Border.all( color: isBumped ? bumpedBg - : colors.surfaceBorder, + : colors.outlineVariant, ), ), child: Row( @@ -1388,7 +1403,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> fontWeight: FontWeight.w700, color: isBumped ? bumpedFg - : colors.textPrimary, + : primaryTextColor, ), ), ), @@ -1410,9 +1425,9 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> child: Container( height: 48, decoration: BoxDecoration( - color: colors.surfaceCardSecondary, + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), - border: Border.all(color: colors.surfaceBorder), + border: Border.all(color: colors.outlineVariant), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -1429,7 +1444,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> fontFamily: AppTypography.fontTelemetry, fontSize: 13, fontWeight: FontWeight.w700, - color: colors.textPrimary, + color: primaryTextColor, ), ), ], @@ -1450,14 +1465,14 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> width: 48, height: 48, decoration: BoxDecoration( - color: colors.surfaceCardSecondary, + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), - border: Border.all(color: colors.surfaceBorder), + border: Border.all(color: colors.outlineVariant), ), child: Icon( Icons.download_rounded, size: 20, - color: colors.textPrimary, + color: primaryTextColor, ), ), ), @@ -1476,7 +1491,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> height: 44, width: double.infinity, decoration: BoxDecoration( - color: colors.surfaceCardSecondary, + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), border: Border.all( color: accentColor.withValues(alpha: 0.35), @@ -1533,9 +1548,9 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), decoration: BoxDecoration( - color: colors.surfaceCardPrimary, + color: colors.surfaceContainer, borderRadius: BorderRadius.circular(10), - border: Border.all(color: colors.surfaceBorder, width: 0.8), + border: Border.all(color: colors.outlineVariant, width: 0.8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1611,13 +1626,27 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> } 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; + return Container( height: 160, decoration: BoxDecoration( - color: const Color(0xFF0B0E13), + color: containerBg, borderRadius: BorderRadius.circular(14), - border: Border.all(color: const Color(0xFF181C21)), + border: Border.all(color: borderColor), ), child: Stack( children: [ @@ -1629,6 +1658,11 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> painter: _FeedRoutePainter( points: hasValidTrack ? item.routePoints : const [], maxLean: item.maxLeanAngleDeg, + accentColor: accentColor, + gridColor: isLight + ? colors.outlineVariant + : AppColors.darkOutlineVariant, + finishColor: secondaryTextColor, ), ), ), @@ -1645,21 +1679,21 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> vertical: 6, ), decoration: BoxDecoration( - color: const Color(0xFF101419).withValues(alpha: 0.88), + color: overlayBg, borderRadius: BorderRadius.circular(8), border: Border.all( - color: const Color(0xFF2A323D), + color: borderColor, ), ), child: Row( mainAxisSize: MainAxisSize.min, - children: const [ + children: [ Icon( Icons.gps_off_rounded, size: 14, - color: Color(0xFF94A3B8), + color: secondaryTextColor, ), - SizedBox(width: 6), + const SizedBox(width: 6), Flexible( child: Text( 'NO GPS TRACK RECORDED', @@ -1670,7 +1704,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> fontSize: 10, fontWeight: FontWeight.w700, letterSpacing: 0.5, - color: Color(0xFFCBD5E1), + color: primaryTextColor, ), ), ), @@ -1687,28 +1721,28 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( - color: const Color(0xFF1D2025).withValues(alpha: 0.85), + color: overlayBg, borderRadius: BorderRadius.circular(6), border: Border.all( - color: AppColors.electricCyan.withValues(alpha: 0.2), + color: accentColor.withValues(alpha: 0.2), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon( + Icon( Icons.landscape_rounded, size: 13, - color: AppColors.electricCyan, + color: accentColor, ), const SizedBox(width: 5), Text( 'ELV +${item.elevationFt} FT', - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 10, fontWeight: FontWeight.w700, - color: Color(0xFFE0E2EA), + color: primaryTextColor, ), ), ], @@ -1725,25 +1759,25 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), decoration: BoxDecoration( - color: const Color(0xFF101419).withValues(alpha: 0.85), + color: overlayBg, borderRadius: BorderRadius.circular(6), border: Border.all( - color: AppColors.electricCyan.withValues(alpha: 0.2), + color: accentColor.withValues(alpha: 0.2), ), ), child: Row( mainAxisSize: MainAxisSize.min, - children: const [ + children: [ Icon(Icons.verified_rounded, - size: 12, color: AppColors.electricCyan), - SizedBox(width: 4), + size: 12, color: accentColor), + const SizedBox(width: 4), Text( 'GPS VERIFIED', style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 9, fontWeight: FontWeight.w800, - color: AppColors.electricCyan, + color: accentColor, ), ), ], @@ -1757,12 +1791,24 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> } 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( height: 180, decoration: BoxDecoration( - color: const Color(0xFF0B0E13), + color: containerBg, borderRadius: BorderRadius.circular(14), - border: Border.all(color: const Color(0xFF181C21)), + border: Border.all(color: borderColor), ), child: ClipRRect( borderRadius: BorderRadius.circular(14), @@ -1771,22 +1817,28 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> children: [ // Atmospheric gradient container mimicking canyon overlook Container( - decoration: const BoxDecoration( + decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [ - Color(0xFF1E293B), - Color(0xFF0F172A), - Color(0xFF020617), - ], + colors: isLight + ? [ + AppColors.lightSurfaceContainerHigh, + AppColors.lightSurfaceContainerHighest, + AppColors.lightOutlineVariant, + ] + : [ + AppColors.darkSurfaceContainer, + AppColors.darkSurface, + AppColors.darkBackground, + ], ), ), - child: const Center( + child: Center( child: Icon( Icons.landscape_rounded, size: 64, - color: Color(0xFF334155), + color: secondaryTextColor, ), ), ), @@ -1822,25 +1874,25 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: const Color(0xFF101419).withValues(alpha: 0.85), + color: overlayBg, borderRadius: BorderRadius.circular(6), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon( + Icon( Icons.photo_camera_rounded, size: 13, - color: AppColors.electricCyan, + color: accentColor, ), const SizedBox(width: 5), Text( item.photoCaption ?? 'Scenic Overlook', - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 11, 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( horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: const Color(0xFF101419).withValues(alpha: 0.85), + color: overlayBg, borderRadius: BorderRadius.circular(6), ), child: Row( mainAxisSize: MainAxisSize.min, - children: const [ + children: [ Icon( Icons.verified_rounded, size: 13, - color: AppColors.electricCyan, + color: accentColor, ), - SizedBox(width: 4), + const SizedBox(width: 4), Text( 'GPS VERIFIED', style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 10, fontWeight: FontWeight.w800, - color: AppColors.electricCyan, + color: accentColor, ), ), ], @@ -1891,10 +1943,16 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard> class _FeedRoutePainter extends CustomPainter { final List points; final double maxLean; + final Color accentColor; + final Color gridColor; + final Color finishColor; const _FeedRoutePainter({ required this.points, required this.maxLean, + required this.accentColor, + required this.gridColor, + required this.finishColor, }); @override @@ -1904,7 +1962,7 @@ class _FeedRoutePainter extends CustomPainter { // 1. Grid lines matrix overlay final gridPaint = Paint() - ..color = const Color(0xFF1D2025) + ..color = gridColor ..strokeWidth = 0.8; const gridSize = 24.0; @@ -1958,9 +2016,9 @@ class _FeedRoutePainter extends CustomPainter { path.lineTo(mappedPoints[i].dx, mappedPoints[i].dy); } - // Cyan glow blur + // Glow blur final glowPaint = Paint() - ..color = AppColors.electricCyan.withValues(alpha: 0.25) + ..color = accentColor.withValues(alpha: 0.25) ..strokeWidth = 8 ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round @@ -1969,7 +2027,7 @@ class _FeedRoutePainter extends CustomPainter { // Active trail final trailPaint = Paint() - ..color = const Color(0xFFC3F5FF) + ..color = accentColor ..strokeWidth = 3.5 ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round @@ -1981,10 +2039,10 @@ class _FeedRoutePainter extends CustomPainter { final apexOffset = mappedPoints[mappedPoints.length ~/ 2]; // Start marker with ring - final startPaint = Paint()..color = AppColors.electricCyan; + final startPaint = Paint()..color = accentColor; canvas.drawCircle(startOffset, 4.5, startPaint); final ringPaint = Paint() - ..color = AppColors.electricCyan.withValues(alpha: 0.6) + ..color = accentColor.withValues(alpha: 0.6) ..style = PaintingStyle.stroke ..strokeWidth = 1.5; canvas.drawCircle(startOffset, 8.0, ringPaint); @@ -1992,16 +2050,16 @@ class _FeedRoutePainter extends CustomPainter { // Apex marker (only if maxLean > 0) final textPainter = TextPainter(textDirection: TextDirection.ltr); if (maxLean > 0) { - final apexPaint = Paint()..color = AppColors.electricCyan; + final apexPaint = Paint()..color = accentColor; canvas.drawCircle(apexOffset, 4.0, apexPaint); textPainter.text = TextSpan( text: '${maxLean.toStringAsFixed(0)}° APEX', - style: const TextStyle( + style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 9, fontWeight: FontWeight.w800, - color: Color(0xFFC3F5FF), + color: accentColor, ), ); textPainter.layout(); @@ -2015,16 +2073,16 @@ class _FeedRoutePainter extends CustomPainter { } // Finish marker - final finishPaint = Paint()..color = const Color(0xFFBAC9CC); + final finishPaint = Paint()..color = finishColor; canvas.drawCircle(finishOffset, 4.0, finishPaint); - textPainter.text = const TextSpan( + textPainter.text = TextSpan( text: 'FINISH', style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 9, fontWeight: FontWeight.w700, - color: Color(0xFFBAC9CC), + color: finishColor, ), ); textPainter.layout(); @@ -2039,6 +2097,10 @@ class _FeedRoutePainter extends CustomPainter { @override 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; } } diff --git a/lib/features/social/feed/widgets/feed_comments_bottom_sheet.dart b/lib/features/social/feed/widgets/feed_comments_bottom_sheet.dart index c15a0a8..114e29d 100644 --- a/lib/features/social/feed/widgets/feed_comments_bottom_sheet.dart +++ b/lib/features/social/feed/widgets/feed_comments_bottom_sheet.dart @@ -103,10 +103,12 @@ class _FeedCommentsBottomSheetState final isLight = Theme.of(context).brightness == Brightness.light; final secondaryTextColor = isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; + final primaryTextColor = + isLight ? AppColors.lightOnSurface : AppColors.darkOnSurface; final accentColor = - isLight ? AppColors.brandBlueStrong : colors.brandCyanBright; - final sendBg = isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; - final sendFg = isLight ? AppColors.onBrand : const Color(0xFF00363D); + isLight ? AppColors.brandBlueStrong : AppColors.brandBlue; + final sendBg = isLight ? AppColors.brandBlueStrong : AppColors.brandBlue; + final sendFg = AppColors.onBrand; final bottomInset = MediaQuery.of(context).viewInsets.bottom; final feedItems = ref.watch(socialFeedNotifierProvider); @@ -124,9 +126,9 @@ class _FeedCommentsBottomSheetState ), child: Container( decoration: BoxDecoration( - color: colors.surfaceCardPrimary, + color: colors.surfaceContainer, borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), - border: Border.all(color: colors.surfaceBorder), + border: Border.all(color: colors.outlineVariant), boxShadow: [ BoxShadow( color: colors.glowShadow, @@ -147,7 +149,7 @@ class _FeedCommentsBottomSheetState width: 44, height: 4, decoration: BoxDecoration( - color: colors.surfaceBorder, + color: colors.outlineVariant, borderRadius: BorderRadius.circular(2), ), ), @@ -169,7 +171,7 @@ class _FeedCommentsBottomSheetState fontFamily: AppTypography.fontHeadline, fontSize: 16, fontWeight: FontWeight.w700, - color: colors.textPrimary, + color: primaryTextColor, ), ), const SizedBox(width: 8), @@ -177,7 +179,7 @@ class _FeedCommentsBottomSheetState padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2), decoration: BoxDecoration( - color: colors.surfaceCardSecondary, + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), border: Border.all( 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 Flexible( @@ -228,7 +230,7 @@ class _FeedCommentsBottomSheetState fontFamily: AppTypography.fontHeadline, fontSize: 14, fontWeight: FontWeight.w600, - color: colors.textPrimary, + color: primaryTextColor, ), ), const SizedBox(height: 4), @@ -256,10 +258,10 @@ class _FeedCommentsBottomSheetState return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: colors.surfaceCardSecondary, + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), border: Border.all( - color: colors.surfaceBorder, + color: colors.outlineVariant, ), ), child: Column( @@ -269,7 +271,7 @@ class _FeedCommentsBottomSheetState children: [ CircleAvatar( radius: 14, - backgroundColor: colors.surfaceCardPrimary, + backgroundColor: colors.surfaceContainer, child: Icon( Icons.sports_motorsports_rounded, size: 16, @@ -289,7 +291,7 @@ class _FeedCommentsBottomSheetState AppTypography.fontHeadline, fontSize: 13, fontWeight: FontWeight.w600, - color: colors.textPrimary, + color: primaryTextColor, ), ), ), @@ -327,7 +329,7 @@ class _FeedCommentsBottomSheetState fontFamily: AppTypography.fontBody, fontSize: 13, 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) Padding( @@ -349,10 +351,10 @@ class _FeedCommentsBottomSheetState child: Container( height: 48, decoration: BoxDecoration( - color: colors.surfaceCardSecondary, + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), border: Border.all( - color: colors.surfaceBorder, + color: colors.outlineVariant, ), ), child: TextField( @@ -360,7 +362,7 @@ class _FeedCommentsBottomSheetState style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 13, - color: colors.textPrimary, + color: primaryTextColor, ), decoration: InputDecoration( hintText: 'Add comment or pace tip...', diff --git a/lib/features/social/feed/widgets/feed_telemetry_inspector_modal.dart b/lib/features/social/feed/widgets/feed_telemetry_inspector_modal.dart index 384eeee..ec697ee 100644 --- a/lib/features/social/feed/widgets/feed_telemetry_inspector_modal.dart +++ b/lib/features/social/feed/widgets/feed_telemetry_inspector_modal.dart @@ -61,8 +61,10 @@ class _FeedTelemetryInspectorModalState final isLight = Theme.of(context).brightness == Brightness.light; final secondaryTextColor = isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; + final primaryTextColor = + isLight ? AppColors.lightOnSurface : AppColors.darkOnSurface; final accentColor = - isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; + isLight ? AppColors.brandBlueStrong : AppColors.brandBlue; final item = widget.feedItem; final mediaQuery = MediaQuery.of(context); final maxHeight = mediaQuery.size.height * 0.90; @@ -70,12 +72,12 @@ class _FeedTelemetryInspectorModalState return Container( constraints: BoxConstraints(maxHeight: maxHeight), decoration: BoxDecoration( - color: colors.surfaceCardPrimary, + color: colors.surfaceContainer, borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), border: Border( - top: BorderSide(color: colors.surfaceBorder, width: 1.5), - left: BorderSide(color: colors.surfaceBorder, width: 1.5), - right: BorderSide(color: colors.surfaceBorder, width: 1.5), + top: BorderSide(color: colors.outlineVariant, width: 1.5), + left: BorderSide(color: colors.outlineVariant, width: 1.5), + right: BorderSide(color: colors.outlineVariant, width: 1.5), ), ), child: SafeArea( @@ -91,7 +93,7 @@ class _FeedTelemetryInspectorModalState width: 40, height: 4, decoration: BoxDecoration( - color: colors.surfaceBorder, + color: colors.outlineVariant, borderRadius: BorderRadius.circular(2), ), ), @@ -132,7 +134,7 @@ class _FeedTelemetryInspectorModalState fontFamily: AppTypography.fontHeadline, fontSize: 15, 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 Flexible( @@ -201,23 +203,23 @@ class _FeedTelemetryInspectorModalState crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 2x3 Telemetry Insets Grid - _buildMetricsGrid(item, colors, isLight, secondaryTextColor, accentColor), + _buildMetricsGrid(item, colors, isLight, secondaryTextColor, accentColor, primaryTextColor), const SizedBox(height: 18), // Trace Selector Tabs - _buildTraceSelector(colors, secondaryTextColor, accentColor), + _buildTraceSelector(colors, secondaryTextColor, accentColor, primaryTextColor), const SizedBox(height: 12), // Interactive Telemetry Graph Canvas - _buildInteractiveGraphCard(item, colors), + _buildInteractiveGraphCard(item, colors, isLight, secondaryTextColor, accentColor, primaryTextColor), const SizedBox(height: 16), // Scrubber Details Pill - _buildScrubberReadout(item, colors, secondaryTextColor, accentColor), + _buildScrubberReadout(item, colors, secondaryTextColor, accentColor, primaryTextColor), const SizedBox(height: 20), // Launch Full Replay Button - _buildReplayButton(context, colors, accentColor), + _buildReplayButton(context, colors, accentColor, primaryTextColor), ], ), ), @@ -234,18 +236,19 @@ class _FeedTelemetryInspectorModalState bool isLight, Color secondaryTextColor, Color accentColor, + Color primaryTextColor, ) { final successColor = - isLight ? AppColors.semanticSuccessLight : const Color(0xFF00E676); + isLight ? AppColors.semanticSuccessLight : AppColors.semanticSuccess; final warnColor = isLight ? AppColors.semanticWarningLight : AppColors.hazardAmber; return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( - color: colors.surfaceCardSecondary, + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(16), - border: Border.all(color: colors.surfaceBorder), + border: Border.all(color: colors.outlineVariant), ), child: Column( children: [ @@ -261,24 +264,24 @@ class _FeedTelemetryInspectorModalState icon: Icons.rotate_right_rounded, ), ), - Container(width: 1, height: 48, color: colors.surfaceBorder), + Container(width: 1, height: 48, color: colors.outlineVariant), Expanded( child: _buildMetricCell( label: 'DISTANCE', value: item.distanceMiles.toStringAsFixed(1), subtext: 'MILES', - accentColor: colors.textPrimary, + accentColor: primaryTextColor, secondaryTextColor: secondaryTextColor, icon: Icons.route_rounded, ), ), - Container(width: 1, height: 48, color: colors.surfaceBorder), + Container(width: 1, height: 48, color: colors.outlineVariant), Expanded( child: _buildMetricCell( label: 'DURATION', value: _formatDuration(item.durationSeconds), subtext: 'MOVING', - accentColor: colors.textPrimary, + accentColor: primaryTextColor, secondaryTextColor: secondaryTextColor, icon: Icons.timer_outlined, ), @@ -286,7 +289,7 @@ class _FeedTelemetryInspectorModalState ], ), const SizedBox(height: 12), - Divider(color: colors.surfaceBorder, height: 1), + Divider(color: colors.outlineVariant, height: 1), const SizedBox(height: 12), Row( children: [ @@ -302,18 +305,18 @@ class _FeedTelemetryInspectorModalState icon: Icons.speed_rounded, ), ), - Container(width: 1, height: 48, color: colors.surfaceBorder), + Container(width: 1, height: 48, color: colors.outlineVariant), Expanded( child: _buildMetricCell( label: 'ELEVATION', value: '+${item.elevationFt}', subtext: 'FEET GAIN', - accentColor: colors.textPrimary, + accentColor: primaryTextColor, secondaryTextColor: secondaryTextColor, icon: Icons.terrain_rounded, ), ), - Container(width: 1, height: 48, color: colors.surfaceBorder), + Container(width: 1, height: 48, color: colors.outlineVariant), Expanded( child: _buildMetricCell( label: 'HAZARDS', @@ -396,13 +399,14 @@ class _FeedTelemetryInspectorModalState MotorSignalThemeColors colors, Color secondaryTextColor, Color accentColor, + Color primaryTextColor, ) { return Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( - color: colors.surfaceCardSecondary, + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), - border: Border.all(color: colors.surfaceBorder), + border: Border.all(color: colors.outlineVariant), ), child: Row( children: [ @@ -413,6 +417,7 @@ class _FeedTelemetryInspectorModalState colors: colors, secondaryTextColor: secondaryTextColor, accentColor: accentColor, + primaryTextColor: primaryTextColor, ), _buildTraceTabItem( tab: _InspectorTraceTab.speedGForce, @@ -421,6 +426,7 @@ class _FeedTelemetryInspectorModalState colors: colors, secondaryTextColor: secondaryTextColor, accentColor: accentColor, + primaryTextColor: primaryTextColor, ), _buildTraceTabItem( tab: _InspectorTraceTab.elevation, @@ -429,6 +435,7 @@ class _FeedTelemetryInspectorModalState colors: colors, secondaryTextColor: secondaryTextColor, accentColor: accentColor, + primaryTextColor: primaryTextColor, ), ], ), @@ -442,6 +449,7 @@ class _FeedTelemetryInspectorModalState required MotorSignalThemeColors colors, required Color secondaryTextColor, required Color accentColor, + required Color primaryTextColor, }) { final isSelected = _selectedTab == tab; return Expanded( @@ -452,7 +460,7 @@ class _FeedTelemetryInspectorModalState duration: const Duration(milliseconds: 180), padding: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( - color: isSelected ? colors.surfaceCardPrimary : Colors.transparent, + color: isSelected ? colors.surfaceContainer : Colors.transparent, borderRadius: BorderRadius.circular(8), border: isSelected ? Border.all(color: accentColor.withValues(alpha: 0.4)) @@ -476,7 +484,7 @@ class _FeedTelemetryInspectorModalState fontSize: 10, fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600, letterSpacing: 0.3, - color: isSelected ? colors.textPrimary : secondaryTextColor, + color: isSelected ? primaryTextColor : secondaryTextColor, ), ), ), @@ -490,14 +498,20 @@ class _FeedTelemetryInspectorModalState Widget _buildInteractiveGraphCard( SocialFeedItem item, MotorSignalThemeColors colors, + bool isLight, + Color secondaryTextColor, + Color accentColor, + Color primaryTextColor, ) { final hasRecorded = item.hasRecordedTelemetry; return Container( height: 220, decoration: BoxDecoration( - color: const Color(0xFF0C1015), + color: isLight + ? AppColors.lightSurfaceContainerHighest + : AppColors.darkSurface, borderRadius: BorderRadius.circular(16), - border: Border.all(color: const Color(0xFF222B38)), + border: Border.all(color: colors.outlineVariant), ), child: ClipRRect( borderRadius: BorderRadius.circular(16), @@ -533,6 +547,12 @@ class _FeedTelemetryInspectorModalState tab: _selectedTab, item: item, scrubFraction: _scrubFraction, + accentColor: accentColor, + gridColor: colors.outlineVariant, + baselineColor: colors.outline, + badgeBgColor: isLight + ? AppColors.lightSurfaceContainerHighest + : AppColors.darkSurfaceContainerHighest, ), ), ); @@ -548,19 +568,22 @@ class _FeedTelemetryInspectorModalState vertical: 14, ), decoration: BoxDecoration( - color: const Color(0xFF141A22).withValues(alpha: 0.92), + color: (isLight + ? AppColors.lightSurface + : AppColors.darkSurfaceContainer) + .withValues(alpha: 0.92), borderRadius: BorderRadius.circular(12), - border: Border.all(color: const Color(0xFF2A3647)), + border: Border.all(color: colors.outlineVariant), ), child: Column( mainAxisSize: MainAxisSize.min, - children: const [ + children: [ Icon( Icons.sensors_off_rounded, size: 24, - color: Color(0xFF8B98A5), + color: secondaryTextColor, ), - SizedBox(height: 8), + const SizedBox(height: 8), Text( 'NO RECORDED TELEMETRY SAMPLES', textAlign: TextAlign.center, @@ -569,17 +592,17 @@ class _FeedTelemetryInspectorModalState fontSize: 11, fontWeight: FontWeight.w800, letterSpacing: 0.5, - color: Colors.white, + color: primaryTextColor, ), ), - SizedBox(height: 4), + const SizedBox(height: 4), Text( 'Detailed multi-trace waveforms require recorded ride telemetry points.', textAlign: TextAlign.center, style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 11, - color: Color(0xFF8B98A5), + color: secondaryTextColor, ), ), ], @@ -597,6 +620,7 @@ class _FeedTelemetryInspectorModalState MotorSignalThemeColors colors, Color secondaryTextColor, Color accentColor, + Color primaryTextColor, ) { final hasRecorded = item.hasRecordedTelemetry; final scrubDist = (item.distanceMiles * _scrubFraction).toStringAsFixed(1); @@ -608,9 +632,9 @@ class _FeedTelemetryInspectorModalState return Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( - color: colors.surfaceCardSecondary, + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), - border: Border.all(color: colors.surfaceBorder), + border: Border.all(color: colors.outlineVariant), ), child: Column( children: [ @@ -660,11 +684,11 @@ class _FeedTelemetryInspectorModalState Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - _buildScrubStat('SPEED', hasRecorded ? '$speedVal MPH' : '-- MPH', colors, secondaryTextColor), - Container(width: 1, height: 24, color: colors.surfaceBorder), - _buildScrubStat('LEAN', hasRecorded ? '$leanVal°' : '--°', colors, secondaryTextColor), - Container(width: 1, height: 24, color: colors.surfaceBorder), - _buildScrubStat('LAT G', hasRecorded ? '${gVal}G' : '--G', colors, secondaryTextColor), + _buildScrubStat('SPEED', hasRecorded ? '$speedVal MPH' : '-- MPH', colors, secondaryTextColor, primaryTextColor), + Container(width: 1, height: 24, color: colors.outlineVariant), + _buildScrubStat('LEAN', hasRecorded ? '$leanVal°' : '--°', colors, secondaryTextColor, primaryTextColor), + Container(width: 1, height: 24, color: colors.outlineVariant), + _buildScrubStat('LAT G', hasRecorded ? '${gVal}G' : '--G', colors, secondaryTextColor, primaryTextColor), ], ), ], @@ -709,6 +733,7 @@ class _FeedTelemetryInspectorModalState String value, MotorSignalThemeColors colors, Color secondaryTextColor, + Color primaryTextColor, ) { return Column( crossAxisAlignment: CrossAxisAlignment.end, @@ -728,7 +753,7 @@ class _FeedTelemetryInspectorModalState fontFamily: AppTypography.fontTelemetry, fontSize: 11, fontWeight: FontWeight.w800, - color: colors.textPrimary, + color: primaryTextColor, ), ), ], @@ -739,12 +764,13 @@ class _FeedTelemetryInspectorModalState BuildContext context, MotorSignalThemeColors colors, Color accentColor, + Color primaryTextColor, ) { return SizedBox( height: 48, child: OutlinedButton.icon( style: OutlinedButton.styleFrom( - backgroundColor: colors.surfaceCardSecondary, + backgroundColor: colors.surfaceContainerHigh, side: BorderSide( color: accentColor.withValues(alpha: 0.5), width: 1.5, @@ -765,7 +791,7 @@ class _FeedTelemetryInspectorModalState fontSize: 12, fontWeight: FontWeight.w800, letterSpacing: 0.8, - color: colors.textPrimary, + color: primaryTextColor, ), ), onPressed: () { @@ -783,11 +809,19 @@ class _TelemetryWaveformPainter extends CustomPainter { final _InspectorTraceTab tab; final SocialFeedItem item; final double scrubFraction; + final Color accentColor; + final Color gridColor; + final Color baselineColor; + final Color badgeBgColor; _TelemetryWaveformPainter({ required this.tab, required this.item, required this.scrubFraction, + required this.accentColor, + required this.gridColor, + required this.baselineColor, + required this.badgeBgColor, }); @override @@ -797,7 +831,7 @@ class _TelemetryWaveformPainter extends CustomPainter { // Draw subtle grid lines final gridPaint = Paint() - ..color = const Color(0xFF19222E) + ..color = gridColor ..strokeWidth = 1.0; for (var i = 1; i <= 4; i++) { @@ -828,7 +862,7 @@ class _TelemetryWaveformPainter extends CustomPainter { // Draw Scrub Cursor Line & Indicator final scrubX = (w * scrubFraction).clamp(0.0, w); final scrubPaint = Paint() - ..color = AppColors.electricCyan + ..color = accentColor ..strokeWidth = 1.8; canvas.drawLine(Offset(scrubX, 0), Offset(scrubX, h), scrubPaint); @@ -847,7 +881,7 @@ class _TelemetryWaveformPainter extends CustomPainter { // Baseline (0° upright) final baselinePaint = Paint() - ..color = const Color(0xFF2A3647) + ..color = baselineColor ..strokeWidth = 1.2 ..style = PaintingStyle.stroke; canvas.drawLine(Offset(0, midY), Offset(w, midY), baselinePaint); @@ -886,25 +920,25 @@ class _TelemetryWaveformPainter extends CustomPainter { ..close(); final fillPaint = Paint() - ..shader = const LinearGradient( + ..shader = LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ - Color(0x3300F2FE), - Color(0x0500F2FE), + accentColor.withValues(alpha: 0.20), + accentColor.withValues(alpha: 0.02), ], ).createShader(Rect.fromLTWH(0, 0, w, h)); canvas.drawPath(fillPath, fillPaint); // Draw Trace Line final linePaint = Paint() - ..color = AppColors.electricCyan + ..color = accentColor ..strokeWidth = 2.5 ..style = PaintingStyle.stroke; canvas.drawPath(path, linePaint); if (peakIndex >= 0 && peakAbsLean > 0) { - const markerColor = Color(0xFF00E676); + final markerColor = AppColors.semanticSuccess; final dotPaint = Paint()..color = markerColor; 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), const Radius.circular(4), ); - final bgPaint = Paint()..color = const Color(0xFF0F151D); + final bgPaint = Paint()..color = badgeBgColor; final borderPaint = Paint() ..color = color.withValues(alpha: 0.5) ..style = PaintingStyle.stroke @@ -1094,6 +1128,10 @@ class _TelemetryWaveformPainter extends CustomPainter { bool shouldRepaint(covariant _TelemetryWaveformPainter oldDelegate) { return oldDelegate.tab != tab || oldDelegate.scrubFraction != scrubFraction || - oldDelegate.item != item; + oldDelegate.item != item || + oldDelegate.accentColor != accentColor || + oldDelegate.gridColor != gridColor || + oldDelegate.baselineColor != baselineColor || + oldDelegate.badgeBgColor != badgeBgColor; } } diff --git a/lib/features/social/feed/widgets/quick_squad_feed_button.dart b/lib/features/social/feed/widgets/quick_squad_feed_button.dart index d1bf900..f941bd4 100644 --- a/lib/features/social/feed/widgets/quick_squad_feed_button.dart +++ b/lib/features/social/feed/widgets/quick_squad_feed_button.dart @@ -2,8 +2,9 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../../../core/theme/app_colors.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 /// 1-tap access to live rider activity, route snapshots, and kudos. @@ -14,15 +15,20 @@ class QuickSquadFeedButton extends StatelessWidget { @override 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) { return IconButton( key: const ValueKey('dash_quick_feed_button'), - icon: const Icon( + icon: Icon( Icons.dynamic_feed_rounded, - color: AppColors.electricCyan, + color: accentColor, size: 19, ), - tooltip: 'Squad & Canyon Feed', + tooltip: 'Squad Feed', padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 32, minHeight: 32), onPressed: () => context.push('/social/feed'), @@ -36,10 +42,10 @@ class QuickSquadFeedButton extends StatelessWidget { child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: const Color(0xFF181C21), + color: colors.surfaceContainer, borderRadius: BorderRadius.circular(16), border: Border.all( - color: AppColors.electricCyan.withValues(alpha: 0.3), + color: colors.outlineVariant, width: 0.8, ), ), @@ -49,26 +55,26 @@ class QuickSquadFeedButton extends StatelessWidget { Container( width: 7, height: 7, - decoration: const BoxDecoration( - color: AppColors.electricCyan, + decoration: BoxDecoration( + color: accentColor, shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: AppColors.electricCyan, + color: accentColor.withValues(alpha: 0.4), blurRadius: 4, ), ], ), ), const SizedBox(width: 5), - const Text( + Text( 'FEED', style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 10, fontWeight: FontWeight.w800, letterSpacing: 0.6, - color: Color(0xFFC3F5FF), + color: textColor, ), ), ], diff --git a/lib/features/social/feed/widgets/share_session_modal.dart b/lib/features/social/feed/widgets/share_session_modal.dart index 7ba3a51..0c3fa91 100644 --- a/lib/features/social/feed/widgets/share_session_modal.dart +++ b/lib/features/social/feed/widgets/share_session_modal.dart @@ -341,9 +341,7 @@ class _ShareSessionModalState extends ConsumerState { authorHandle: authorHandle, authorBadge: 'PRO', clubName: 'Bay Area Twisties Club', - locationName: _locationController.text.trim().isNotEmpty - ? _locationController.text.trim() - : 'Location unavailable', + locationName: _locationController.text.trim(), rideTitle: _titleController.text.trim().isNotEmpty ? _titleController.text.trim() : 'Morning Ride', @@ -369,22 +367,26 @@ class _ShareSessionModalState extends ConsumerState { ref.read(feedFilterStateProvider.notifier).state = FeedFilter.allActivity; if (mounted) { + final colors = context.motorColors; + final isLight = Theme.of(context).brightness == Brightness.light; Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( - backgroundColor: const Color(0xFF1D2025), + backgroundColor: colors.surfaceContainerHigh, content: Row( - children: const [ - Icon(Icons.check_circle_rounded, - color: AppColors.electricCyan, size: 20), - SizedBox(width: 10), + children: [ + const Icon(Icons.check_circle_rounded, + color: AppColors.semanticSuccess, size: 20), + const SizedBox(width: 10), Expanded( child: Text( 'Ride session published to Community Feed!', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 13, - color: Colors.white, + color: isLight + ? AppColors.lightOnSurface + : AppColors.darkOnSurface, ), ), ), @@ -406,9 +408,11 @@ class _ShareSessionModalState extends ConsumerState { final isLight = Theme.of(context).brightness == Brightness.light; final secondaryTextColor = isLight ? AppColors.lightOnSurfaceVariant : AppColors.darkOnSurfaceVariant; + final primaryTextColor = + isLight ? AppColors.lightOnSurface : AppColors.darkOnSurface; final accentColor = - isLight ? AppColors.brandBlueStrong : AppColors.electricCyan; - final buttonFg = isLight ? AppColors.onBrand : const Color(0xFF00363D); + isLight ? AppColors.brandBlueStrong : AppColors.brandBlue; + final buttonFg = AppColors.onBrand; final bottomInset = MediaQuery.of(context).viewInsets.bottom; return ConstrainedBox( @@ -417,9 +421,9 @@ class _ShareSessionModalState extends ConsumerState { ), child: Container( decoration: BoxDecoration( - color: colors.surfaceCardPrimary, + color: colors.surfaceContainer, borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), - border: Border.all(color: colors.surfaceBorder), + border: Border.all(color: colors.outlineVariant), boxShadow: [ BoxShadow( color: colors.glowShadow, @@ -449,7 +453,7 @@ class _ShareSessionModalState extends ConsumerState { width: 44, height: 4, decoration: BoxDecoration( - color: colors.surfaceBorder, + color: colors.outlineVariant, borderRadius: BorderRadius.circular(2), ), ), @@ -485,7 +489,7 @@ class _ShareSessionModalState extends ConsumerState { fontFamily: AppTypography.fontHeadline, fontSize: 16, fontWeight: FontWeight.w700, - color: colors.textPrimary, + color: primaryTextColor, ), ), Text( @@ -527,21 +531,21 @@ class _ShareSessionModalState extends ConsumerState { Container( padding: const EdgeInsets.symmetric(horizontal: 14), decoration: BoxDecoration( - color: colors.surfaceCardSecondary, + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(10), - border: Border.all(color: accentColor.withValues(alpha: 0.4)), + border: Border.all(color: colors.outlineVariant), ), child: DropdownButtonHideUnderline( child: DropdownButton( key: const ValueKey('share_session_ride_picker'), value: _selectedRide, isExpanded: true, - dropdownColor: colors.surfaceCardSecondary, + dropdownColor: colors.surfaceContainerHigh, style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 13, fontWeight: FontWeight.w600, - color: colors.textPrimary, + color: primaryTextColor, ), items: _recentRides.map((ride) { final dist = ride.distanceMiles > 0 ? ride.distanceMiles : ride.distance; @@ -578,7 +582,7 @@ class _ShareSessionModalState extends ConsumerState { style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 14, - color: colors.textPrimary, + color: primaryTextColor, ), decoration: _buildInputDecoration( hint: 'e.g. Sunday Skyline & Pescadero Carve', @@ -598,7 +602,7 @@ class _ShareSessionModalState extends ConsumerState { style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 14, - color: colors.textPrimary, + color: primaryTextColor, ), decoration: _buildInputDecoration( hint: 'e.g. 2024 Ducati Streetfighter V4', @@ -616,20 +620,20 @@ class _ShareSessionModalState extends ConsumerState { Container( padding: const EdgeInsets.symmetric(horizontal: 14), decoration: BoxDecoration( - color: colors.surfaceCardSecondary, + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(10), - border: Border.all(color: colors.surfaceBorder), + border: Border.all(color: colors.outlineVariant), ), child: DropdownButtonHideUnderline( child: DropdownButton( value: _selectedDiscipline, isExpanded: true, - dropdownColor: colors.surfaceCardSecondary, + dropdownColor: colors.surfaceContainerHigh, style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 13, fontWeight: FontWeight.w600, - color: colors.textPrimary, + color: primaryTextColor, ), items: RidingDiscipline.values .where((d) => d != RidingDiscipline.all) @@ -655,7 +659,7 @@ class _ShareSessionModalState extends ConsumerState { style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 14, - color: colors.textPrimary, + color: primaryTextColor, ), decoration: _buildInputDecoration( hint: 'e.g. Saratoga, CA or Trailhead', @@ -829,6 +833,10 @@ class _ShareSessionModalState extends ConsumerState { required Color accentColor, bool highlight = false, }) { + final primaryTextColor = colors.surfaceContainer == AppColors.darkSurfaceContainer + ? AppColors.darkOnSurface + : AppColors.lightOnSurface; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -838,9 +846,9 @@ class _ShareSessionModalState extends ConsumerState { width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 11), decoration: BoxDecoration( - color: colors.surfaceCardSecondary, + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(10), - border: Border.all(color: colors.surfaceBorder), + border: Border.all(color: colors.outlineVariant), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -853,7 +861,7 @@ class _ShareSessionModalState extends ConsumerState { fontFamily: AppTypography.fontTelemetry, fontSize: 13, fontWeight: FontWeight.w700, - color: highlight ? accentColor : colors.textPrimary, + color: highlight ? accentColor : primaryTextColor, ), ), ), @@ -903,15 +911,15 @@ class _ShareSessionModalState extends ConsumerState { color: secondaryTextColor, ), filled: true, - fillColor: colors.surfaceCardSecondary, + fillColor: colors.surfaceContainerHigh, contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: colors.surfaceBorder), + borderSide: BorderSide(color: colors.outlineVariant), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: colors.surfaceBorder), + borderSide: BorderSide(color: colors.outlineVariant), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), diff --git a/lib/features/social/social_hub_screen.dart b/lib/features/social/social_hub_screen.dart index b981e6e..74fdd31 100644 --- a/lib/features/social/social_hub_screen.dart +++ b/lib/features/social/social_hub_screen.dart @@ -251,7 +251,7 @@ class _SocialHubScreenState extends ConsumerState { key: const ValueKey('social_card_feed'), icon: Icons.dynamic_feed_outlined, title: 'Community Feed', - subtitle: 'Squad telemetry, route snapshots, canyon paces & fist-bump kudos', + subtitle: 'Squad telemetry, route snapshots, paces & kudos', colors: colors, onTap: () => setState(() => _currentTab = SocialHubTab.feed), ), @@ -276,7 +276,7 @@ class _SocialHubScreenState extends ConsumerState { key: const ValueKey('social_card_feed'), icon: Icons.dynamic_feed_outlined, title: 'Community Feed', - subtitle: 'Squad telemetry, route snapshots, canyon paces & fist-bump kudos', + subtitle: 'Squad telemetry, route snapshots, paces & kudos', colors: colors, onTap: () => setState(() => _currentTab = SocialHubTab.feed), ), diff --git a/lib/services/routes/aws_location_route_provider.dart b/lib/services/routes/aws_location_route_provider.dart index 06ff35d..4b63eb2 100644 --- a/lib/services/routes/aws_location_route_provider.dart +++ b/lib/services/routes/aws_location_route_provider.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math' as math; +import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import '../map/map_provider_interface.dart'; import 'route_models.dart'; @@ -41,6 +42,31 @@ class AwsLocationRouteProvider implements RouteProvider { @override 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 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 Future> searchPlaces( String query, { @@ -287,27 +313,8 @@ class AwsLocationRouteProvider implements RouteProvider { final address = first['Address'] as Map?; if (address == null) return null; - // Extract coarse locality and region/state — strictly avoid street addresses or exact house numbers - final locality = address['Locality']?.toString().trim(); - 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; + // Extract only coarse locality and administrative area. + return buildCoarseLocationLabel(address); } catch (_) { return null; } diff --git a/lib/services/social/social_feed_models.dart b/lib/services/social/social_feed_models.dart index b61b564..d3679dc 100644 --- a/lib/services/social/social_feed_models.dart +++ b/lib/services/social/social_feed_models.dart @@ -609,7 +609,7 @@ class SocialFeedItem { authorAvatarPreset: json['authorAvatarPreset'] as String?, authorBadge: json['authorBadge'] 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(), rideTitle: json['rideTitle'] as String? ?? 'Afternoon Carve', bikeModel: json['bikeModel'] as String? ?? 'Motorcycle', diff --git a/test/aws_location_route_provider_test.dart b/test/aws_location_route_provider_test.dart index a06996a..598f88b 100644 --- a/test/aws_location_route_provider_test.dart +++ b/test/aws_location_route_provider_test.dart @@ -9,6 +9,29 @@ import 'package:motorcycle_dash/services/routes/route_provider.dart'; void main() { 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', () { final provider = AwsLocationRouteProvider( apiKey: 'test-key', diff --git a/test/community_social_feed_test.dart b/test/community_social_feed_test.dart index cd6c036..f390f08 100644 --- a/test/community_social_feed_test.dart +++ b/test/community_social_feed_test.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.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/database/database.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_service.dart'; -Widget _createTestApp({required Widget child, List overrides = const []}) { +Widget _createTestApp({ + required Widget child, + List overrides = const [], + ThemeData? theme, +}) { return ProviderScope( overrides: overrides, child: MaterialApp( - theme: ThemeData.dark().copyWith( - extensions: const [ - MotorSignalThemeColors.dark, - ], - ), + theme: theme ?? + ThemeData.dark().copyWith( + colorScheme: const ColorScheme.dark(primary: AppColors.brandBlue), + extensions: const [ + MotorSignalThemeColors.dark, + ], + ), home: Scaffold(body: child), ), ); @@ -105,6 +112,16 @@ void main() { expect(json['distanceMiles'], 68.4); 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', () { @@ -196,6 +213,41 @@ void main() { }); 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 { await tester.binding.setSurfaceSize(const Size(400, 800)); @@ -208,7 +260,7 @@ void main() { await tester.pump(const Duration(milliseconds: 50)); 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.text('All Activity'), findsOneWidget); expect(find.text('Club Only'), findsOneWidget); @@ -898,8 +950,8 @@ void main() { find.text('All Activity'), ); final selectedFilterBg = isLight - ? const Color(0xFF0047B3) - : const Color(0xFF00E5FF); + ? AppColors.brandBlueStrong + : AppColors.brandBlue; expect( contrastRatio(selectedFilterText.style!.color!, selectedFilterBg), 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(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(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(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(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(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(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(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(lightShareBtnFinder); + expect(lightShareBtn.style?.foregroundColor?.resolve({}), AppColors.onBrand); + + // Verify selected filter tab uses brandBlueStrong in light theme + final lightAnimatedContainers = tester.widgetList(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(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); + } + } + }); + }); } diff --git a/test/social_hub_navigation_test.dart b/test/social_hub_navigation_test.dart index b86076f..35eed4f 100644 --- a/test/social_hub_navigation_test.dart +++ b/test/social_hub_navigation_test.dart @@ -47,7 +47,7 @@ void main() { // 3. We land directly on Community Feed! 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 final hubToggle = find.byKey(const ValueKey('social_tab_hub')); @@ -63,6 +63,6 @@ void main() { await tester.tap(feedToggle); await tester.pumpAndSettle(); - expect(find.text('SQUAD & CANYON FEED'), findsOneWidget); + expect(find.text('SQUAD FEED'), findsOneWidget); }); } diff --git a/test/social_hybrid_ui_test.dart b/test/social_hybrid_ui_test.dart index a57a9fa..00b3de9 100644 --- a/test/social_hybrid_ui_test.dart +++ b/test/social_hybrid_ui_test.dart @@ -78,7 +78,7 @@ void main() { // Verify header and filter components render cleanly 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.text('SHARE RECENT SESSION'), findsOneWidget);