Correctly handle the state

The problem was that some of the previous changes were asynchronous and would
not always represent the actual current state. This solution works, but is
a bit verbose and perhaps overly complicated?
This commit is contained in:
Roland Geider
2025-02-22 23:09:59 +01:00
parent eecaa6fcf0
commit 2f975b1fd3
2 changed files with 106 additions and 100 deletions

View File

@@ -1,9 +1,9 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logging/logging.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:wger/models/exercises/exercise.dart';
const GYM_PAGE_KEY = 'gym_current_page';
const DEFAULT_DURATION = Duration(hours: 5);
final StateNotifierProvider<GymStateNotifier, GymState> gymStateProvider =
StateNotifierProvider<GymStateNotifier, GymState>((ref) {
@@ -15,56 +15,54 @@ class GymState {
final bool showExercisePages;
final int currentPage;
final int? dayId;
late DateTime validUntil;
const GymState({
this.exercisePages = const {},
this.showExercisePages = true,
this.currentPage = 0,
this.dayId = null,
});
GymState(
{this.exercisePages = const {},
this.showExercisePages = true,
this.currentPage = 0,
this.dayId = null,
DateTime? validUntil}) {
this.validUntil = validUntil ?? DateTime.now().add(DEFAULT_DURATION);
}
GymState copyWith({
Map<Exercise, int>? exercisePages,
bool? showExercisePages,
int? currentPage,
int? dayId,
DateTime? validUntil,
}) {
return GymState(
exercisePages: exercisePages ?? this.exercisePages,
showExercisePages: showExercisePages ?? this.showExercisePages,
currentPage: currentPage ?? this.currentPage,
dayId: dayId ?? this.dayId,
validUntil: validUntil ?? this.validUntil.add(DEFAULT_DURATION),
);
}
@override
String toString() {
return 'GymState(currentPage: $currentPage, showExercisePages: $showExercisePages, exercisePages: ${exercisePages.length} exercises, dayId: $dayId)';
return 'GymState('
'currentPage: $currentPage, '
'showExercisePages: $showExercisePages, '
'exercisePages: ${exercisePages.length} exercises, '
'dayId: $dayId, '
'validUntil: $validUntil '
')';
}
}
class GymStateNotifier extends StateNotifier<GymState> {
final _prefs = SharedPreferences.getInstance();
final _logger = Logger('GymStateNotifier');
GymStateNotifier() : super(const GymState()) {
debugPrint('GymStateNotifier: Initializing');
_loadSavedState();
}
GymStateNotifier() : super(GymState());
Future<void> _loadSavedState() async {
debugPrint('GymStateNotifier: Loading saved state');
final SharedPreferences prefs = await _prefs;
final int savedPage = prefs.getInt(GYM_PAGE_KEY) ?? 0;
debugPrint('GymStateNotifier: Loaded saved page: $savedPage');
state = state.copyWith(currentPage: savedPage);
}
Future<void> setCurrentPage(int page) async {
debugPrint('GymStateNotifier: Setting page from ${state.currentPage} to $page');
final SharedPreferences prefs = await _prefs;
await prefs.setInt(GYM_PAGE_KEY, page);
void setCurrentPage(int page) {
// _logger.fine('Setting page from ${state.currentPage} to $page');
state = state.copyWith(currentPage: page);
debugPrint('GymStateNotifier: New state - $state');
}
void toggleExercisePages() {
@@ -72,26 +70,24 @@ class GymStateNotifier extends StateNotifier<GymState> {
}
void setDayId(int dayId) {
// _logger.fine('Setting day id from ${state.dayId} to $dayId');
state = state.copyWith(dayId: dayId);
}
void setExercisePages(Map<Exercise, int> exercisePages) {
debugPrint('GymStateNotifier: Setting exercise pages - ${exercisePages.length} exercises');
// _logger.fine('Setting exercise pages - ${exercisePages.length} exercises');
state = state.copyWith(exercisePages: exercisePages);
debugPrint(
'GymStateNotifier: Exercise pages set - ${exercisePages.entries.map((e) => '${e.key.id}: ${e.value}').join(', ')}');
debugPrint('GymStateNotifier: New state - $state');
// _logger.fine(
// 'Exercise pages set - ${exercisePages.entries.map((e) => '${e.key.id}: ${e.value}').join(', ')}');
}
Future<void> clear() async {
debugPrint('GymStateNotifier: Clearing state');
final SharedPreferences prefs = await _prefs;
await prefs.remove(GYM_PAGE_KEY);
void clear() {
_logger.fine('Clearing state');
state = state.copyWith(
exercisePages: {},
currentPage: 0,
dayId: null,
validUntil: DateTime.now().add(DEFAULT_DURATION),
);
debugPrint('GymStateNotifier: State cleared - $state');
}
}

View File

@@ -22,6 +22,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart' as provider;
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/helpers/consts.dart';
@@ -54,6 +55,7 @@ class GymMode extends ConsumerStatefulWidget {
final DayData _dayDataDisplay;
final int _iteration;
late final TimeOfDay _start;
final _logger = Logger('GymMode');
GymMode(this._dayDataGym, this._dayDataDisplay, this._iteration) {
_start = TimeOfDay.now();
@@ -65,6 +67,8 @@ class GymMode extends ConsumerStatefulWidget {
class _GymModeState extends ConsumerState<GymMode> {
var _totalElements = 1;
late Future<int> _initData;
bool _initialPageJumped = false;
/// Map with the first (navigation) page for each exercise
final Map<Exercise, int> _exercisePages = {};
@@ -79,52 +83,46 @@ class _GymModeState extends ConsumerState<GymMode> {
@override
void initState() {
super.initState();
_initData = _loadGymState();
_controller = PageController(initialPage: 0);
_calculatePages();
// Initialize the controller with the current page
final initialPage = ref.read(gymStateProvider).currentPage;
_controller = PageController(initialPage: initialPage);
// Delay state modifications until after the widget tree is built
Future.microtask(() {
// Get the saved page and day from the provider
final savedPage = ref.read(gymStateProvider).currentPage;
final savedDayId = ref.read(gymStateProvider).dayId;
debugPrint('Saved Page: $savedPage');
debugPrint('Saved Day ID: $savedDayId');
// Set the dayId in the state
ref.read(gymStateProvider.notifier).setDayId(widget._dayDataGym.day!.id!);
debugPrint('Current Day ID: ${widget._dayDataGym.day!.id}');
// Check if the current day is different from the saved day
if (widget._dayDataGym.day!.id != savedDayId) {
// Reset the saved page to 0
ref.read(gymStateProvider.notifier).setCurrentPage(0);
debugPrint('Different day detected. Resetting to page 0.');
} else {
// Use the saved page
ref.read(gymStateProvider.notifier).setCurrentPage(savedPage);
debugPrint('Same day detected. Using saved page: $savedPage');
}
// Calculate the pages
_calculatePages();
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(gymStateProvider.notifier).setExercisePages(_exercisePages);
});
}
Future<int> _loadGymState() async {
final validUntil = ref.read(gymStateProvider).validUntil;
final currentPage = ref.read(gymStateProvider).currentPage;
final savedDayId = ref.read(gymStateProvider).dayId;
final newDayId = widget._dayDataGym.day!.id!;
final shouldReset =
widget._dayDataGym.day!.id != savedDayId || validUntil.isBefore(DateTime.now());
widget._logger.fine('Day ID mismatch or expired validUntil date. Resetting to page 0.');
final initialPage = shouldReset ? 0 : currentPage;
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(gymStateProvider.notifier)
..setDayId(newDayId)
..setCurrentPage(initialPage);
});
return initialPage;
}
void _calculatePages() {
// for (final slot in widget._dayDataGym.slots) {
// _totalElements += slot.setConfigs.length;
// }
_totalElements = 1;
for (final slot in widget._dayDataGym.slots) {
_totalElements += slot.setConfigs.length;
}
_exercisePages.clear();
var currentPage = 1;
for (final slot in widget._dayDataGym.slots) {
var firstPage = true;
for (final config in slot.setConfigs) {
final exercise = provider.Provider.of<ExercisesProvider>(context, listen: false)
.findExerciseById(config.exerciseId);
final exercise = context.read<ExercisesProvider>().findExerciseById(config.exerciseId);
if (firstPage) {
_exercisePages[exercise] = currentPage;
@@ -134,8 +132,6 @@ class _GymModeState extends ConsumerState<GymMode> {
firstPage = false;
}
}
ref.read(gymStateProvider.notifier).setExercisePages(_exercisePages);
}
List<Widget> getContent() {
@@ -181,34 +177,48 @@ class _GymModeState extends ConsumerState<GymMode> {
@override
Widget build(BuildContext context) {
final List<Widget> children = [
StartPage(_controller, widget._dayDataDisplay, _exercisePages),
...getContent(),
SessionPage(
provider.Provider.of<RoutinesProvider>(context, listen: false)
.findById(widget._dayDataGym.day!.routineId),
_controller,
widget._start,
_exercisePages,
),
];
return PageView(
controller: _controller,
onPageChanged: (page) {
// Update the current page
ref.read(gymStateProvider.notifier).setCurrentPage(page);
debugPrint('Current Page: $page');
debugPrint('Total Pages: ${children.length}');
// Check if the last page is reached
if (page == children.length - 1) {
ref.read(gymStateProvider.notifier).clear();
debugPrint('Last page reached. Resetting state and navigation.');
// Reset the navigation stack
_controller.jumpToPage(0);
}
},
children: children,
);
return FutureBuilder<int>(
future: _initData,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
final initialPage = snapshot.data!;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!_initialPageJumped && _controller.hasClients) {
_controller.jumpToPage(initialPage);
setState(() => _initialPageJumped = true);
}
});
final List<Widget> children = [
StartPage(_controller, widget._dayDataDisplay, _exercisePages),
...getContent(),
SessionPage(
provider.Provider.of<RoutinesProvider>(context, listen: false)
.findById(widget._dayDataGym.day!.routineId),
_controller,
widget._start,
_exercisePages,
),
];
return PageView(
controller: _controller,
onPageChanged: (page) {
ref.read(gymStateProvider.notifier).setCurrentPage(page);
// Check if the last page is reached
if (page == children.length - 1) {
ref.read(gymStateProvider.notifier).clear();
}
},
children: children,
);
}
});
}
}