feat: improve weight redability

This commit is contained in:
Patryk Hegenberg 2026-01-04 18:01:10 +01:00
parent a5efbf8dad
commit fc3e1d98ef
4 changed files with 468 additions and 90 deletions

View file

@ -259,23 +259,37 @@ class _InventoryScreenState extends ConsumerState<InventoryScreen> {
.titleMedium
?.copyWith(color: AppTheme.textSecondary)),
const SizedBox(height: 8),
SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Row(
children: [
ActionChip(
label: const Text('Home Gym'),
onPressed: () => _applyPreset('home')),
const SizedBox(width: 8),
ActionChip(
label: const Text('Commercial'),
onPressed: () => _applyPreset('commercial')),
const SizedBox(width: 8),
ActionChip(
label: const Text('Minimal'),
onPressed: () => _applyPreset('minimal')),
],
),
LayoutBuilder(
builder: (context, constraints) {
final screenWidth = constraints.maxWidth;
final chipWidth = 130.0;
final spacing = 8.0;
final totalWidth = (chipWidth * 3) + (spacing * 2);
if (screenWidth < totalWidth) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildPresetChip('Home Gym', 'home'),
const SizedBox(height: 8),
_buildPresetChip('Commercial', 'commercial'),
const SizedBox(height: 8),
_buildPresetChip('Minimal', 'minimal'),
],
);
} else {
return Row(
children: [
Expanded(child: _buildPresetChip('Home Gym', 'home')),
const SizedBox(width: 8),
Expanded(
child: _buildPresetChip('Commercial', 'commercial')),
const SizedBox(width: 8),
Expanded(child: _buildPresetChip('Minimal', 'minimal')),
],
);
}
},
),
const SizedBox(height: 24),
Text('Plates Available',
@ -303,33 +317,56 @@ class _InventoryScreenState extends ConsumerState<InventoryScreen> {
.titleMedium
?.copyWith(color: AppTheme.textSecondary)),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: _bandInventory.entries.map((entry) {
final resistance = AppConstants.defaultBands[entry.key] ?? 0;
return FilterChip(
label: Text('${entry.key} (~${resistance.toInt()}kg)'),
selected: entry.value,
onSelected: (bool selected) {
setState(() {
_bandInventory[entry.key] = selected;
_hasChanges = true;
});
LayoutBuilder(
builder: (context, constraints) {
final screenWidth = constraints.maxWidth;
int crossAxisCount = 2;
if (screenWidth > 600) {
crossAxisCount = 4;
} else if (screenWidth > 400) {
crossAxisCount = 2;
}
final bandEntries = _bandInventory.entries.toList();
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 2.5,
),
itemCount: bandEntries.length,
itemBuilder: (context, index) {
final entry = bandEntries[index];
final resistance =
AppConstants.defaultBands[entry.key] ?? 0;
return _buildBandChip(
entry.key,
resistance.toInt(),
entry.value,
);
},
selectedColor:
_getBandColor(entry.key).withValues(alpha: 0.3),
checkmarkColor: _getBandColor(entry.key),
side: BorderSide(color: _getBandColor(entry.key)),
);
}).toList(),
},
),
const SizedBox(height: 40),
if (_hasChanges)
ElevatedButton(
onPressed: _isLoading ? null : _saveChanges,
child: _isLoading
? const CircularProgressIndicator(color: Colors.black)
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
color: Colors.black,
strokeWidth: 2,
),
)
: const Text('SAVE CHANGES'),
),
],
@ -337,4 +374,67 @@ class _InventoryScreenState extends ConsumerState<InventoryScreen> {
),
);
}
Widget _buildPresetChip(String label, String preset) {
return OutlinedButton(
onPressed: () => _applyPreset(preset),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
side: BorderSide(
color: AppTheme.primaryColor.withValues(alpha: 0.5),
),
),
child: Text(label),
);
}
Widget _buildBandChip(String color, int resistance, bool isSelected) {
return InkWell(
onTap: () {
setState(() {
_bandInventory[color] = !isSelected;
_hasChanges = true;
});
},
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: isSelected
? _getBandColor(color).withValues(alpha: 0.2)
: AppTheme.surfaceColor,
border: Border.all(
color: _getBandColor(color),
width: isSelected ? 2 : 1,
),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (isSelected)
Icon(
Icons.check_circle,
color: _getBandColor(color),
size: 18,
),
if (isSelected) const SizedBox(width: 4),
Expanded(
child: Text(
'$color (~${resistance}kg)',
style: TextStyle(
color: isSelected
? _getBandColor(color)
: AppTheme.textSecondary,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
fontSize: 12,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
),
);
}
}

View file

@ -19,6 +19,16 @@ class PlateCounter extends StatelessWidget {
return colorValue != null ? Color(colorValue) : Colors.grey;
}
Color _getTextColor(double weight) {
if (weight == 5.0) {
return Colors.black;
}
if (weight <= 2.5) {
return Colors.white70;
}
return Colors.white;
}
@override
Widget build(BuildContext context) {
return Card(
@ -43,8 +53,8 @@ class PlateCounter extends StatelessWidget {
weight == weight.toInt()
? '${weight.toInt()}'
: weight.toStringAsFixed(2),
style: const TextStyle(
color: Colors.white,
style: TextStyle(
color: _getTextColor(weight),
fontWeight: FontWeight.bold,
fontSize: 12,
),

View file

@ -82,7 +82,9 @@ class _InventorySetupScreenState extends ConsumerState<InventorySetupScreen> {
void _handleNext() {
final platesList = <double>[];
_plateInventory.forEach((weight, count) {
for (int i = 0; i < count; i++) platesList.add(weight);
for (int i = 0; i < count; i++) {
platesList.add(weight);
}
});
final bandsList = <Map<String, dynamic>>[];
@ -166,8 +168,7 @@ class _InventorySetupScreenState extends ConsumerState<InventorySetupScreen> {
}
if (mounted) {
ref.read(onboardingDataProvider.notifier).state = {};
ref.read(onboardingDataProvider.notifier).clear();
context.go('/hub');
}
} catch (e, stackTrace) {
@ -278,29 +279,39 @@ class _InventorySetupScreenState extends ConsumerState<InventorySetupScreen> {
?.copyWith(color: AppTheme.textPrimary),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => _applyPreset('home'),
child: const Text('Home Gym'),
),
),
const SizedBox(width: 8),
Expanded(
child: OutlinedButton(
onPressed: () => _applyPreset('commercial'),
child: const Text('Commercial'),
),
),
const SizedBox(width: 8),
Expanded(
child: OutlinedButton(
onPressed: () => _applyPreset('minimal'),
child: const Text('Minimal'),
),
),
],
LayoutBuilder(
builder: (context, constraints) {
final screenWidth = constraints.maxWidth;
final chipWidth = 130.0;
final spacing = 8.0;
final totalWidth = (chipWidth * 3) + (spacing * 2);
if (screenWidth < totalWidth) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildPresetButton('Home Gym', 'home'),
const SizedBox(height: 8),
_buildPresetButton('Commercial', 'commercial'),
const SizedBox(height: 8),
_buildPresetButton('Minimal', 'minimal'),
],
);
} else {
return Row(
children: [
Expanded(child: _buildPresetButton('Home Gym', 'home')),
const SizedBox(width: 8),
Expanded(
child:
_buildPresetButton('Commercial', 'commercial')),
const SizedBox(width: 8),
Expanded(
child: _buildPresetButton('Minimal', 'minimal')),
],
);
}
},
),
const SizedBox(height: 32),
Text(
@ -339,35 +350,56 @@ class _InventorySetupScreenState extends ConsumerState<InventorySetupScreen> {
?.copyWith(color: AppTheme.textSecondary),
),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 8,
children: _bandInventory.entries.map((entry) {
final resistance = AppConstants.defaultBands[entry.key] ?? 0;
return FilterChip(
label: Text('${entry.key} (~${resistance.toInt()}kg)'),
selected: entry.value,
onSelected: (bool selected) {
setState(() {
_bandInventory[entry.key] = selected;
});
LayoutBuilder(
builder: (context, constraints) {
final screenWidth = constraints.maxWidth;
int crossAxisCount = 2;
if (screenWidth > 600) {
crossAxisCount = 4;
} else if (screenWidth > 400) {
crossAxisCount = 2;
}
final bandEntries = _bandInventory.entries.toList();
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 2.5,
),
itemCount: bandEntries.length,
itemBuilder: (context, index) {
final entry = bandEntries[index];
final resistance =
AppConstants.defaultBands[entry.key] ?? 0;
return _buildBandChip(
entry.key,
resistance.toInt(),
entry.value,
);
},
selectedColor:
_getBandColor(entry.key).withValues(alpha: 0.3),
checkmarkColor: _getBandColor(entry.key),
labelStyle: TextStyle(
color: entry.value ? Colors.white : Colors.grey,
),
side: BorderSide(
color: _getBandColor(entry.key),
),
);
}).toList(),
},
),
const SizedBox(height: 32),
ElevatedButton(
onPressed: _handleNext,
child: const Text('NEXT STEP'),
onPressed: _isLoading ? null : _handleNext,
child: _isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
color: Colors.black,
strokeWidth: 2,
),
)
: const Text('NEXT STEP'),
),
],
),
@ -375,4 +407,61 @@ class _InventorySetupScreenState extends ConsumerState<InventorySetupScreen> {
),
);
}
Widget _buildPresetButton(String label, String preset) {
return OutlinedButton(
onPressed: () => _applyPreset(preset),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
),
child: Text(label),
);
}
Widget _buildBandChip(String color, int resistance, bool isSelected) {
return InkWell(
onTap: () {
setState(() {
_bandInventory[color] = !isSelected;
});
},
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: isSelected
? _getBandColor(color).withValues(alpha: 0.2)
: AppTheme.surfaceColor,
border: Border.all(
color: _getBandColor(color),
width: isSelected ? 2 : 1,
),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (isSelected)
Icon(
Icons.check_circle,
color: _getBandColor(color),
size: 18,
),
if (isSelected) const SizedBox(width: 4),
Expanded(
child: Text(
'$color (~${resistance}kg)',
style: TextStyle(
color: isSelected ? Colors.white : AppTheme.textSecondary,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
fontSize: 12,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
),
);
}
}

View file

@ -1,3 +1,172 @@
// import 'package:flutter/material.dart';
// import '../../../../core/theme/app_theme.dart';
// import '../../../../core/constants/asset_paths.dart';
// class PlateVisualizer extends StatelessWidget {
// final List<double> plateConfiguration;
// final bool isTwoSided;
// final String exerciseName;
// const PlateVisualizer({
// super.key,
// required this.plateConfiguration,
// required this.isTwoSided,
// required this.exerciseName,
// });
// Color _getPlateColor(double weight) {
// final colorValue = PlateColors.colors[weight];
// return colorValue != null ? Color(colorValue) : Colors.grey;
// }
// Color _getTextColor(double weight) {
// if (weight == 5.0) {
// return Colors.black;
// }
// if (weight <= 2.5) {
// return Colors.white70;
// }
// return Colors.white;
// }
// @override
// Widget build(BuildContext context) {
// if (plateConfiguration.isEmpty) {
// return Card(
// child: Padding(
// padding: const EdgeInsets.all(32),
// child: Column(
// children: [
// Icon(
// isTwoSided ? Icons.fitness_center : Icons.accessibility,
// size: 64,
// color: AppTheme.primaryColor.withValues(alpha: 0.5),
// ),
// const SizedBox(height: 16),
// Text(
// isTwoSided ? 'Bar Only' : 'Bodyweight Only',
// style: Theme.of(context)
// .textTheme
// .titleLarge
// ?.copyWith(color: AppTheme.textSecondary),
// ),
// ],
// ),
// ),
// );
// }
// return Card(
// child: Padding(
// padding: const EdgeInsets.all(16),
// child: Column(
// children: [
// Text(
// isTwoSided ? 'Load Per Side' : 'Load on Belt',
// style: Theme.of(context).textTheme.titleMedium,
// ),
// const SizedBox(height: 16),
// if (isTwoSided) _buildBarbellView() else _buildBeltView(),
// const SizedBox(height: 16),
// Text(
// 'Total: ${plateConfiguration.fold<double>(0, (sum, p) => sum + p).toStringAsFixed(1)} kg ${isTwoSided ? 'per side' : ''}',
// style: Theme.of(context).textTheme.bodyMedium?.copyWith(
// color: AppTheme.primaryColor,
// ),
// ),
// ],
// ),
// ),
// );
// }
// Widget _buildBarbellView() {
// return SizedBox(
// height: 120,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// Container(
// width: 8,
// height: 80,
// color: Colors.grey[800],
// ),
// ...plateConfiguration.map((weight) {
// final size = _getPlateSize(weight);
// return Container(
// width: 20,
// height: size,
// margin: const EdgeInsets.symmetric(horizontal: 2),
// decoration: BoxDecoration(
// color: _getPlateColor(weight),
// border: Border.all(color: Colors.white24, width: 2),
// borderRadius: BorderRadius.circular(4),
// ),
// );
// }).toList(),
// Container(
// width: 40,
// height: 20,
// decoration: BoxDecoration(
// color: Colors.grey[700],
// borderRadius: BorderRadius.circular(4),
// ),
// child: Center(
// child: Container(
// width: 30,
// height: 10,
// decoration: BoxDecoration(
// color: Colors.grey[600],
// borderRadius: BorderRadius.circular(2),
// ),
// ),
// ),
// ),
// ],
// ),
// );
// }
// Widget _buildBeltView() {
// return Wrap(
// spacing: 8,
// runSpacing: 8,
// alignment: WrapAlignment.center,
// children: plateConfiguration.map((weight) {
// return Container(
// width: _getPlateSize(weight) * 0.8,
// height: _getPlateSize(weight) * 0.8,
// decoration: BoxDecoration(
// color: _getPlateColor(weight),
// shape: BoxShape.circle,
// border: Border.all(color: Colors.white24, width: 3),
// ),
// child: Center(
// child: Text(
// weight == weight.toInt()
// ? '${weight.toInt()}'
// : weight.toStringAsFixed(2),
// style: const TextStyle(
// color: Colors.white,
// fontWeight: FontWeight.bold,
// fontSize: 14,
// ),
// ),
// ),
// );
// }).toList(),
// );
// }
// double _getPlateSize(double weight) {
// if (weight >= 20) return 120.0;
// if (weight >= 10) return 100.0;
// if (weight >= 5) return 80.0;
// return 60.0;
// }
// }
import 'package:flutter/material.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/constants/asset_paths.dart';
@ -19,6 +188,16 @@ class PlateVisualizer extends StatelessWidget {
return colorValue != null ? Color(colorValue) : Colors.grey;
}
Color _getTextColor(double weight) {
if (weight == 5.0) {
return Colors.black;
}
if (weight <= 2.5) {
return Colors.white70;
}
return Colors.white;
}
@override
Widget build(BuildContext context) {
if (plateConfiguration.isEmpty) {
@ -137,8 +316,8 @@ class PlateVisualizer extends StatelessWidget {
weight == weight.toInt()
? '${weight.toInt()}'
: weight.toStringAsFixed(2),
style: const TextStyle(
color: Colors.white,
style: TextStyle(
color: _getTextColor(weight),
fontWeight: FontWeight.bold,
fontSize: 14,
),