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:
parent
b005bb00ae
commit
2d1bfbeef6
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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...',
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue