mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Polish the add plate widget
This commit is contained in:
@@ -130,3 +130,5 @@ const LIBERAPAY_URL = 'https://liberapay.com/wger';
|
||||
/// the milliseconds themselves can cause the application to crash since it runs
|
||||
/// out of memory...
|
||||
const double CHART_MILLISECOND_FACTOR = 100000.0;
|
||||
|
||||
enum WeightUnitEnum { kg, lb }
|
||||
|
||||
@@ -19,13 +19,14 @@
|
||||
/// Calculates the number of plates needed to reach a specific weight
|
||||
List<num> plateCalculator(num totalWeight, num barWeight, List<num> plates) {
|
||||
final List<num> result = [];
|
||||
final sortedPlates = List.of(plates)..sort();
|
||||
|
||||
// Weight is less than the bar
|
||||
if (totalWeight < barWeight) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (plates.isEmpty) {
|
||||
if (sortedPlates.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -33,12 +34,12 @@ List<num> plateCalculator(num totalWeight, num barWeight, List<num> plates) {
|
||||
totalWeight = (totalWeight - barWeight) / 2;
|
||||
|
||||
// Weight can't be divided with the smallest plate
|
||||
if (totalWeight % plates.first > 0) {
|
||||
if (totalWeight % sortedPlates.first > 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Iterate through the plates, beginning with the biggest ones
|
||||
for (final plate in plates.reversed) {
|
||||
for (final plate in sortedPlates.reversed) {
|
||||
while (totalWeight >= plate) {
|
||||
totalWeight -= plate;
|
||||
result.add(plate);
|
||||
|
||||
@@ -90,20 +90,20 @@ void main() async {
|
||||
await PreferenceHelper.instance.migrationSupportFunctionForSharedPreferences();
|
||||
|
||||
// Catch errors from Flutter itself (widget build, layout, paint, etc.)
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
final stack = details.stack ?? StackTrace.empty;
|
||||
if (kDebugMode) {
|
||||
FlutterError.dumpErrorToConsole(details);
|
||||
}
|
||||
|
||||
// Don't show the full error dialog for network image loading errors.
|
||||
if (details.exception is NetworkImageLoadException) {
|
||||
return;
|
||||
}
|
||||
|
||||
showGeneralErrorDialog(details.exception, stack);
|
||||
// throw details.exception;
|
||||
};
|
||||
// FlutterError.onError = (FlutterErrorDetails details) {
|
||||
// final stack = details.stack ?? StackTrace.empty;
|
||||
// if (kDebugMode) {
|
||||
// FlutterError.dumpErrorToConsole(details);
|
||||
// }
|
||||
//
|
||||
// // Don't show the full error dialog for network image loading errors.
|
||||
// if (details.exception is NetworkImageLoadException) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // showGeneralErrorDialog(details.exception, stack);
|
||||
// throw details.exception;
|
||||
// };
|
||||
|
||||
// Catch errors that happen outside of the Flutter framework (e.g., in async operations)
|
||||
PlatformDispatcher.instance.onError = (error, stack) {
|
||||
@@ -114,8 +114,8 @@ void main() async {
|
||||
if (error is WgerHttpException) {
|
||||
showHttpExceptionErrorDialog(error);
|
||||
} else {
|
||||
showGeneralErrorDialog(error, stack);
|
||||
// throw error;
|
||||
// showGeneralErrorDialog(error, stack);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Return true to indicate that the error has been handled.
|
||||
|
||||
@@ -2,10 +2,14 @@ import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/gym_mode.dart';
|
||||
import 'package:wger/helpers/shared_preferences.dart';
|
||||
|
||||
const DEFAULT_KG_PLATES = [2.5, 5, 10, 15, 20, 25];
|
||||
const DEFAULT_LB_PLATES = [2.5, 5, 10, 25, 35, 45];
|
||||
|
||||
const PREFS_KEY_PLATES = 'selectedPlates';
|
||||
|
||||
@@ -14,6 +18,12 @@ final plateWeightsProvider = StateNotifierProvider<PlateWeightsNotifier, PlateWe
|
||||
});
|
||||
|
||||
class PlateWeightsState {
|
||||
final _logger = Logger('PlateWeightsState');
|
||||
|
||||
final barWeightKg = 20;
|
||||
final barWeightLb = 45;
|
||||
|
||||
// https://en.wikipedia.org/wiki/Barbell#Bumper_plates
|
||||
final Map<double, Color> plateColorMapKg = {
|
||||
25: Colors.red,
|
||||
20: Colors.blue,
|
||||
@@ -33,26 +43,29 @@ class PlateWeightsState {
|
||||
25: Colors.green,
|
||||
10: Colors.white,
|
||||
5: Colors.blue,
|
||||
2.5: Colors.green,
|
||||
1.25: Colors.white,
|
||||
};
|
||||
|
||||
final bool isMetric;
|
||||
final num totalWeight;
|
||||
final num barWeight;
|
||||
final List<num> selectedPlates;
|
||||
final List<num> kgWeights = const [0.5, 1, 1.25, 2, 2.5, 5, 10, 15, 20, 25];
|
||||
final List<num> lbsWeights = const [2.5, 5, 10, 25, 35, 45];
|
||||
final List<num> availablePlatesKg = const [0.5, 1, 1.25, 2, 2.5, 5, 10, 15, 20, 25];
|
||||
final List<num> availablePlatesLb = const [2.5, 5, 10, 25, 35, 45];
|
||||
|
||||
PlateWeightsState({
|
||||
this.isMetric = true,
|
||||
this.totalWeight = 0,
|
||||
this.barWeight = 20,
|
||||
List<num>? selectedPlates,
|
||||
}) : selectedPlates = selectedPlates ?? [...DEFAULT_KG_PLATES];
|
||||
|
||||
num get totalWeightInKg => isMetric ? totalWeight : totalWeight / 2.205;
|
||||
num get barWeight {
|
||||
return isMetric ? barWeightKg : barWeightLb;
|
||||
}
|
||||
|
||||
num get barWeightInKg => isMetric ? barWeight : barWeight / 2.205;
|
||||
List<num> get availablePlates {
|
||||
return isMetric ? availablePlatesKg : availablePlatesLb;
|
||||
}
|
||||
|
||||
List<num> get platesList {
|
||||
return plateCalculator(totalWeight, barWeight, selectedPlates);
|
||||
@@ -63,8 +76,7 @@ class PlateWeightsState {
|
||||
}
|
||||
|
||||
Map<num, int> get calculatePlates {
|
||||
List<num> sortedPlates = List.from(selectedPlates)..sort();
|
||||
return groupPlates(plateCalculator(totalWeight, barWeight, sortedPlates));
|
||||
return groupPlates(plateCalculator(totalWeight, barWeight, selectedPlates));
|
||||
}
|
||||
|
||||
Color getColor(num plate) {
|
||||
@@ -74,91 +86,87 @@ class PlateWeightsState {
|
||||
return plateColorMapLb[plate.toDouble()] ?? Colors.grey;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {'isMetric': isMetric, 'selectedPlates': selectedPlates};
|
||||
}
|
||||
|
||||
PlateWeightsState copyWith({
|
||||
bool? isMetric,
|
||||
num? totalWeight,
|
||||
num? barWeight,
|
||||
List<num>? selectedPlates,
|
||||
}) {
|
||||
return PlateWeightsState(
|
||||
isMetric: isMetric ?? this.isMetric,
|
||||
totalWeight: totalWeight ?? this.totalWeight,
|
||||
barWeight: barWeight ?? this.barWeight,
|
||||
selectedPlates: selectedPlates ?? this.selectedPlates,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PlateWeightsNotifier extends StateNotifier<PlateWeightsState> {
|
||||
PlateWeightsNotifier() : super(PlateWeightsState()) {
|
||||
_readPlates();
|
||||
final _logger = Logger('PlateWeightsNotifier');
|
||||
|
||||
late SharedPreferencesAsync prefs;
|
||||
|
||||
PlateWeightsNotifier({SharedPreferencesAsync? prefs}) : super(PlateWeightsState()) {
|
||||
this.prefs = prefs ?? PreferenceHelper.asyncPref;
|
||||
_readDataFromSharedPrefs();
|
||||
}
|
||||
|
||||
Future<void> _saveIntoSharedPrefs() async {
|
||||
final pref = await SharedPreferences.getInstance();
|
||||
pref.setString(PREFS_KEY_PLATES, jsonEncode(state.selectedPlates));
|
||||
Future<void> saveToSharedPrefs() async {
|
||||
await prefs.setString(PREFS_KEY_PLATES, jsonEncode(state.toJson()));
|
||||
}
|
||||
|
||||
Future<void> _readPlates() async {
|
||||
final pref = await SharedPreferences.getInstance();
|
||||
final platePrefData = pref.getString(PREFS_KEY_PLATES);
|
||||
if (platePrefData != null) {
|
||||
Future<void> _readDataFromSharedPrefs() async {
|
||||
final prefsData = await prefs.getString(PREFS_KEY_PLATES);
|
||||
|
||||
if (prefsData != null) {
|
||||
try {
|
||||
final plateData = json.decode(platePrefData);
|
||||
if (plateData is List) {
|
||||
state = state.copyWith(selectedPlates: plateData.cast<num>());
|
||||
} else {
|
||||
throw const FormatException('Not a List');
|
||||
}
|
||||
final plateData = json.decode(prefsData);
|
||||
state = state.copyWith(
|
||||
isMetric: plateData['isMetric'] ?? true,
|
||||
selectedPlates: plateData['selectedPlates'].cast<num>() ?? [...DEFAULT_KG_PLATES],
|
||||
);
|
||||
} catch (e) {
|
||||
state = state.copyWith(selectedPlates: []);
|
||||
_logger.fine('Error decoding plate data from SharedPreferences: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleSelection(num x) async {
|
||||
final newSelectedPlates = List<num>.from(state.selectedPlates);
|
||||
final newSelectedPlates = List.of(state.selectedPlates);
|
||||
if (newSelectedPlates.contains(x)) {
|
||||
newSelectedPlates.remove(x);
|
||||
} else {
|
||||
newSelectedPlates.add(x);
|
||||
}
|
||||
state = state.copyWith(selectedPlates: newSelectedPlates);
|
||||
await _saveIntoSharedPrefs();
|
||||
await saveToSharedPrefs();
|
||||
}
|
||||
|
||||
void unitChange() {
|
||||
if (state.isMetric == false) {
|
||||
void unitChange({WeightUnitEnum? unit}) {
|
||||
final WeightUnitEnum changeTo =
|
||||
unit ?? (state.isMetric ? WeightUnitEnum.lb : WeightUnitEnum.kg);
|
||||
|
||||
if (changeTo == WeightUnitEnum.kg && !state.isMetric) {
|
||||
state = state.copyWith(
|
||||
isMetric: true,
|
||||
totalWeight: state.totalWeightInKg,
|
||||
barWeight: state.barWeightInKg,
|
||||
totalWeight: 0,
|
||||
selectedPlates: [...DEFAULT_KG_PLATES],
|
||||
);
|
||||
} else {
|
||||
}
|
||||
|
||||
if (changeTo == WeightUnitEnum.lb && state.isMetric) {
|
||||
state = state.copyWith(
|
||||
isMetric: false,
|
||||
totalWeight: state.totalWeightInKg * 2.205,
|
||||
barWeight: state.barWeightInKg * 2.205,
|
||||
totalWeight: 0,
|
||||
selectedPlates: [...DEFAULT_LB_PLATES],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void clear() async {
|
||||
state = state.copyWith(selectedPlates: []);
|
||||
await _saveIntoSharedPrefs();
|
||||
}
|
||||
|
||||
void setWeight(num x) {
|
||||
_logger.fine('Setting weight to $x');
|
||||
state = state.copyWith(totalWeight: x);
|
||||
}
|
||||
|
||||
void resetPlates() async {
|
||||
state = state.copyWith(selectedPlates: [...DEFAULT_KG_PLATES]);
|
||||
await _saveIntoSharedPrefs();
|
||||
}
|
||||
|
||||
void selectAllPlates() async {
|
||||
state = state.copyWith(selectedPlates: [...state.kgWeights]);
|
||||
await _saveIntoSharedPrefs();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,13 +46,12 @@ class UserProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
// change the unit of plates
|
||||
void unitChange() {
|
||||
if (profile?.weightUnitStr == 'kg') {
|
||||
void changeUnit({changeTo = 'kg'}) {
|
||||
if (changeTo == 'kg') {
|
||||
profile?.weightUnitStr = 'lb';
|
||||
} else {
|
||||
profile?.weightUnitStr = 'kg';
|
||||
}
|
||||
ChangeNotifier();
|
||||
}
|
||||
|
||||
// Load theme mode from SharedPreferences
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:provider/provider.dart' as provider;
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/providers/plate_weights.dart';
|
||||
import 'package:wger/providers/user.dart';
|
||||
import 'package:wger/widgets/routines/plate_calculator.dart';
|
||||
|
||||
class AddPlateWeights extends ConsumerStatefulWidget {
|
||||
const AddPlateWeights({super.key});
|
||||
@@ -13,8 +16,7 @@ class AddPlateWeights extends ConsumerStatefulWidget {
|
||||
|
||||
class _AddPlateWeightsState extends ConsumerState<AddPlateWeights>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<Offset> _animation;
|
||||
final _unitController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -22,151 +24,79 @@ class _AddPlateWeightsState extends ConsumerState<AddPlateWeights>
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref.read(plateWeightsProvider.notifier);
|
||||
});
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(seconds: 1),
|
||||
);
|
||||
_animation = Tween<Offset>(
|
||||
begin: const Offset(-1.0, 0.0),
|
||||
end: Offset.zero,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeInOut,
|
||||
));
|
||||
|
||||
_controller.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_unitController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = AppLocalizations.of(context);
|
||||
|
||||
final plateWeightsState = ref.watch(plateWeightsProvider);
|
||||
final plateWeightsNotifier = ref.read(plateWeightsProvider.notifier);
|
||||
final userProviderInstance = provider.Provider.of<UserProvider>(context);
|
||||
final userProfile = userProviderInstance.profile;
|
||||
final userProvider = provider.Provider.of<UserProvider>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Select Available Plates')),
|
||||
body: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('Preferred Unit'),
|
||||
DropdownButton<String>(
|
||||
value: (userProfile?.isMetric ?? true) ? 'kg' : 'lbs',
|
||||
onChanged: (String? newValue) {
|
||||
if (newValue == null) return;
|
||||
final selectedUnitIsMetric = (newValue == 'kg');
|
||||
if (selectedUnitIsMetric != (userProfile?.isMetric ?? true)) {
|
||||
plateWeightsNotifier.unitChange();
|
||||
provider.Provider.of<UserProvider>(context, listen: false).unitChange();
|
||||
_controller.reset();
|
||||
_controller.forward();
|
||||
}
|
||||
},
|
||||
items: ['kg', 'lbs'].map((unit) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: unit,
|
||||
child: Text(unit),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: DropdownMenu<WeightUnitEnum>(
|
||||
width: double.infinity,
|
||||
initialSelection: plateWeightsState.isMetric ? WeightUnitEnum.kg : WeightUnitEnum.lb,
|
||||
controller: _unitController,
|
||||
requestFocusOnTap: true,
|
||||
label: Text(i18n.unit),
|
||||
onSelected: (WeightUnitEnum? unit) {
|
||||
if (unit == null) {
|
||||
return;
|
||||
}
|
||||
plateWeightsNotifier.unitChange(unit: unit);
|
||||
// userProvider.changeUnit(changeTo: unit.name);
|
||||
// userProvider.saveProfile();
|
||||
},
|
||||
dropdownMenuEntries: WeightUnitEnum.values.map((unit) {
|
||||
return DropdownMenuEntry<WeightUnitEnum>(
|
||||
value: unit,
|
||||
label: unit == WeightUnitEnum.kg ? i18n.kg : i18n.lb,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
const double widthThreshold = 450.0;
|
||||
final int crossAxisCount = constraints.maxWidth > widthThreshold ? 10 : 5;
|
||||
return GridView.count(
|
||||
crossAxisCount: crossAxisCount,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: plateWeightsState.availablePlates.map((number) {
|
||||
final bool isSelected = plateWeightsState.selectedPlates.contains(number);
|
||||
return GestureDetector(
|
||||
onTap: () => plateWeightsNotifier.toggleSelection(number),
|
||||
child: PlateWeight(
|
||||
value: number,
|
||||
isSelected: isSelected,
|
||||
color: plateWeightsState.getColor(number),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
runAlignment: WrapAlignment.center,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: (userProfile == null || userProfile.isMetric)
|
||||
? plateWeightsState.kgWeights.map((number) {
|
||||
return SlideTransition(
|
||||
position: _animation,
|
||||
child: GestureDetector(
|
||||
onTap: () => plateWeightsNotifier.toggleSelection(number),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
height: 50,
|
||||
width: 50,
|
||||
alignment: Alignment.center,
|
||||
margin: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: plateWeightsState.selectedPlates.contains(number)
|
||||
? plateWeightsState.getColor(number)
|
||||
: const Color.fromARGB(255, 97, 105, 101),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.black, width: 2),
|
||||
),
|
||||
child: Text(
|
||||
number.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList()
|
||||
: plateWeightsState.lbsWeights.map((number) {
|
||||
return SlideTransition(
|
||||
position: _animation,
|
||||
child: GestureDetector(
|
||||
onTap: () => plateWeightsNotifier.toggleSelection(number),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(8),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: plateWeightsState.selectedPlates.contains(number)
|
||||
? const Color.fromARGB(255, 82, 226, 236)
|
||||
: const Color.fromARGB(255, 97, 105, 101),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
'$number lbs',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text('Done'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
plateWeightsNotifier.selectAllPlates();
|
||||
},
|
||||
child: const Text('Select all'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
plateWeightsNotifier.resetPlates();
|
||||
},
|
||||
child: const Text('Reset'),
|
||||
),
|
||||
],
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
plateWeightsNotifier.saveToSharedPrefs();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(i18n.save),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -36,6 +36,7 @@ import 'package:wger/widgets/routines/forms/reps_unit.dart';
|
||||
import 'package:wger/widgets/routines/forms/rir.dart';
|
||||
import 'package:wger/widgets/routines/forms/weight_unit.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/navigation.dart';
|
||||
import 'package:wger/widgets/routines/plate_calculator.dart';
|
||||
|
||||
class LogPage extends ConsumerStatefulWidget {
|
||||
final PageController _controller;
|
||||
@@ -379,21 +380,26 @@ class _LogPageState extends ConsumerState<LogPage> {
|
||||
|
||||
Widget getPlates() {
|
||||
final plateWeightsState = ref.watch(plateWeightsProvider);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).plateCalculator,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (context) => const AddPlateWeights()));
|
||||
},
|
||||
icon: const Icon(Icons.settings),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).plateCalculator,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (context) => const AddPlateWeights()));
|
||||
},
|
||||
icon: const Icon(Icons.settings),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 35,
|
||||
child: plateWeightsState.hasPlates
|
||||
? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -403,28 +409,12 @@ class _LogPageState extends ConsumerState<LogPage> {
|
||||
children: [
|
||||
Text(entry.value.toString()),
|
||||
const Text('×'),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: ref.read(plateWeightsProvider).getColor(entry.key),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.black, width: 1),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 3),
|
||||
child: SizedBox(
|
||||
height: 35,
|
||||
width: 35,
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
entry.key.toString(),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
PlateWeight(
|
||||
value: entry.key,
|
||||
size: 37,
|
||||
padding: 2,
|
||||
margin: 0,
|
||||
color: ref.read(plateWeightsProvider).getColor(entry.key),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
|
||||
48
lib/widgets/routines/plate_calculator.dart
Normal file
48
lib/widgets/routines/plate_calculator.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PlateWeight extends StatelessWidget {
|
||||
final num value;
|
||||
final Color color;
|
||||
final bool isSelected;
|
||||
final double size;
|
||||
final double padding;
|
||||
final double margin;
|
||||
|
||||
const PlateWeight({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.color,
|
||||
this.isSelected = true,
|
||||
this.size = 50,
|
||||
this.padding = 8,
|
||||
this.margin = 3,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: size,
|
||||
width: size,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(padding),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
margin: EdgeInsets.all(margin),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? color : Colors.black12,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.black, width: isSelected ? 1 : 0),
|
||||
),
|
||||
child: Text(
|
||||
value.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: isSelected ? Colors.black : Colors.black87,
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1181,10 +1181,11 @@ class MockUserProvider extends _i1.Mock implements _i22.UserProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
void unitChange() => super.noSuchMethod(
|
||||
void changeUnit({dynamic changeTo = 'kg'}) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#unitChange,
|
||||
#changeUnit,
|
||||
[],
|
||||
{#changeTo: changeTo},
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@@ -593,10 +593,11 @@ class MockUserProvider extends _i1.Mock implements _i17.UserProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
void unitChange() => super.noSuchMethod(
|
||||
void changeUnit({dynamic changeTo = 'kg'}) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#unitChange,
|
||||
#changeUnit,
|
||||
[],
|
||||
{#changeTo: changeTo},
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@@ -340,10 +340,11 @@ class MockUserProvider extends _i1.Mock implements _i13.UserProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
void unitChange() => super.noSuchMethod(
|
||||
void changeUnit({dynamic changeTo = 'kg'}) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#unitChange,
|
||||
#changeUnit,
|
||||
[],
|
||||
{#changeTo: changeTo},
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user