diff --git a/ios/Flutter/ephemeral/flutter_lldb_helper.py b/ios/Flutter/ephemeral/flutter_lldb_helper.py new file mode 100644 index 00000000..a88caf99 --- /dev/null +++ b/ios/Flutter/ephemeral/flutter_lldb_helper.py @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +import lldb + +def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): + """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" + base = frame.register["x0"].GetValueAsAddress() + page_len = frame.register["x1"].GetValueAsUnsigned() + + # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the + # first page to see if handled it correctly. This makes diagnosing + # misconfiguration (e.g. missing breakpoint) easier. + data = bytearray(page_len) + data[0:8] = b'IHELPED!' + + error = lldb.SBError() + frame.GetThread().GetProcess().WriteMemory(base, data, error) + if not error.Success(): + print(f'Failed to write into {base}[+{page_len}]', error) + return + +def __lldb_init_module(debugger: lldb.SBDebugger, _): + target = debugger.GetDummyTarget() + # Caveat: must use BreakpointCreateByRegEx here and not + # BreakpointCreateByName. For some reasons callback function does not + # get carried over from dummy target for the later. + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) + bp.SetAutoContinue(True) + print("-- LLDB integration loaded --") diff --git a/ios/Flutter/ephemeral/flutter_lldbinit b/ios/Flutter/ephemeral/flutter_lldbinit new file mode 100644 index 00000000..e3ba6fbe --- /dev/null +++ b/ios/Flutter/ephemeral/flutter_lldbinit @@ -0,0 +1,5 @@ +# +# Generated file, do not edit. +# + +command script import --relative-to-command-file flutter_lldb_helper.py diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 40a9062d..5c98ddd0 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1138,11 +1138,11 @@ "@impressionBad": {}, "gymModeShowExercises": "Übersichtsseiten der Übungen anzeigen", "@gymModeShowExercises": {}, - "gymModeShowTimer": "Zeige Timer zwischen den Sätzen", + "gymModeShowTimer": "Timer zwischen Sätzen anzeigen", "@gymModeShowTimer": {}, "gymModeTimerType": "Timer-Typ", "@gymModeTimerType": {}, - "gymModeTimerTypeHelText": "Wenn ein Satz eine Pausenzeit hat, wird immer ein Countdown verwendet.", + "gymModeTimerTypeHelText": "Wenn ein Satz eine Pausenzeit hat, wird immer ein Countdown genutzt.", "@gymModeTimerTypeHelText": {}, "countdown": "Countdown", "@countdown": {}, diff --git a/lib/widgets/routines/gym_mode/log_page.dart b/lib/widgets/routines/gym_mode/log_page.dart index d4fa2575..007dbcea 100644 --- a/lib/widgets/routines/gym_mode/log_page.dart +++ b/lib/widgets/routines/gym_mode/log_page.dart @@ -1,6 +1,6 @@ /* * This file is part of wger Workout Manager . - * Copyright (C) 2020, 2021 wger Team + * Copyright (C) 2020, 2025 wger Team * * wger Workout Manager is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -15,6 +15,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; @@ -50,7 +51,7 @@ class LogPage extends ConsumerStatefulWidget { } class _LogPageState extends ConsumerState { - final GlobalKey<_LogFormWidgetState> _logFormKey = GlobalKey<_LogFormWidgetState>(); + late void Function(Log) _copyFromPastLog = (_) {}; late FocusNode focusNode; @@ -70,6 +71,7 @@ class _LogPageState extends ConsumerState { Widget build(BuildContext context) { final theme = Theme.of(context); final state = ref.watch(gymStateProvider); + final languageCode = Localizations.localeOf(context).languageCode; final page = state.getPageByIndex(); if (page == null) { @@ -101,7 +103,7 @@ class _LogPageState extends ConsumerState { return Column( children: [ NavigationHeader( - log.exercise.getTranslation(Localizations.localeOf(context).languageCode).name, + log.exercise.getTranslation(languageCode).name, widget._controller, ), @@ -153,7 +155,8 @@ class _LogPageState extends ConsumerState { log: log, pastLogs: state.routine.filterLogsByExercise(log.exercise.id!), onCopy: (pastLog) { - _logFormKey.currentState?.copyFromPastLog(pastLog); + // Call the function registered by the child + _copyFromPastLog(pastLog); }, setStateCallback: (fn) { setState(fn); @@ -170,11 +173,11 @@ class _LogPageState extends ConsumerState { child: Padding( padding: const EdgeInsets.symmetric(vertical: 5), child: LogFormWidget( - key: _logFormKey, controller: widget._controller, configData: setConfigData, log: log, focusNode: focusNode, + registerCopy: (fn) => _copyFromPastLog = fn, ), ), ), @@ -502,6 +505,8 @@ class LogFormWidget extends ConsumerStatefulWidget { final SetConfigData configData; final Log log; final FocusNode focusNode; + // Callback used by the child to register its copy function with the parent. + final void Function(void Function(Log))? registerCopy; LogFormWidget({ super.key, @@ -509,6 +514,7 @@ class LogFormWidget extends ConsumerStatefulWidget { required this.configData, required this.log, required this.focusNode, + this.registerCopy, }); @override @@ -532,20 +538,53 @@ class _LogFormWidgetState extends ConsumerState { _repetitionsController = TextEditingController(); _weightController = TextEditingController(); + // Register the copy function with the parent + widget.registerCopy?.call(copyFromPastLog); + WidgetsBinding.instance.addPostFrameCallback((_) { - final locale = Localizations.localeOf(context).toString(); - final numberFormat = NumberFormat.decimalPattern(locale); - - if (widget.configData.repetitions != null) { - _repetitionsController.text = numberFormat.format(widget.configData.repetitions); - } - - if (widget.configData.weight != null) { - _weightController.text = numberFormat.format(widget.configData.weight); - } + _syncControllersWithWidget(); }); } + @override + void didUpdateWidget(covariant LogFormWidget oldWidget) { + super.didUpdateWidget(oldWidget); + + // If the log or config changed, update internal _log and controllers + if (oldWidget.log != widget.log || oldWidget.configData != widget.configData) { + _log = widget.log; + _syncControllersWithWidget(); + } + + // If the parent replaced the registerCopy callback, register again + if (oldWidget.registerCopy != widget.registerCopy) { + widget.registerCopy?.call(copyFromPastLog); + } + } + + void _syncControllersWithWidget() { + final locale = Localizations.localeOf(context).toString(); + final numberFormat = NumberFormat.decimalPattern(locale); + + // Priority: current log -> config defaults -> empty + try { + _repetitionsController.text = widget.log.repetitions != null + ? numberFormat.format(widget.log.repetitions) + : (widget.configData.repetitions != null + ? numberFormat.format(widget.configData.repetitions) + : ''); + + _weightController.text = widget.log.weight != null + ? numberFormat.format(widget.log.weight) + : (widget.configData.weight != null ? numberFormat.format(widget.configData.weight) : ''); + } on Exception catch (e) { + // Defensive fallback: set empty strings if formatting fails + widget._logger.fine('Error syncing controllers: $e'); + _repetitionsController.text = ''; + _weightController.text = ''; + } + } + @override void dispose() { _repetitionsController.dispose();