From 42db6ccc6d1243e14d16ce0d83b231d1614eb3ba Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Wed, 30 Oct 2024 11:35:17 +0100 Subject: [PATCH 01/64] New Crowdin updates (#417) * New translations en.yaml (German) * New translations en.yaml (German) * New translations en.yaml (Hungarian) * New translations en.yaml (German) * New translations en.yaml (German) * New translations en.yaml (Hungarian) * New translations en.yaml (German) * New translations en.yaml (Hungarian) --- assets/translations/de.yaml | 35 +++++++++++++++++------------------ assets/translations/hu.yaml | 5 ++++- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/assets/translations/de.yaml b/assets/translations/de.yaml index 2d06bb4df..1707f78e0 100644 --- a/assets/translations/de.yaml +++ b/assets/translations/de.yaml @@ -899,33 +899,32 @@ components: error: config: title: Fehler beim Abrufen der Druckerkonfiguration! - body: Beim Abrufen der Druckerkonfiguration ist ein Fehler aufgetreten. Stellen Sie sicher, - dass die Maschine erreichbar ist und Mobileraker verbunden ist. + body: Beim Abrufen der Druckerkonfiguration ist ein Fehler aufgetreten. Stellen Sie sicher, dass die Maschine erreichbar ist und Mobileraker verbunden ist. gcode_preview_settings_sheet: - title: 'Anzeige-Einstellungen' + title: Anzeige-Einstellungen show_grid: - title: 'Gitter anzeigen' - subtitle: 'Zeigt ein Referenzgitter' + title: Gitter anzeigen + subtitle: Zeigt ein Referenzgitter show_axes: - title: 'Achsen anzeigen' - subtitle: 'Zeigt die X-, Y-Achsen an' + title: Achsen anzeigen + subtitle: Zeigt die X-, Y-Achsen an show_next_layer: - title: 'Nächste Schicht anzeigen' - subtitle: 'Zeigt die nächste Schicht an' + title: Nächste Schicht anzeigen + subtitle: Zeigt die nächste Schicht an show_previous_layer: - title: 'Vorherige Schicht anzeigen' - subtitle: 'Zeigt die zuvor gedruckte Schicht an' + title: Vorherige Schicht anzeigen + subtitle: Zeigt die zuvor gedruckte Schicht an extrusion_width_multiplier: - prefix: 'Linienbreiten-Multiplikator' + prefix: Linienbreiten-Multiplikator show_extrusion: - title: 'Extrusionen anzeigen' - subtitle: 'Zeige Materialbewegungen in der Vorschau an' + title: Extrusionen anzeigen + subtitle: Zeige Materialbewegungen in der Vorschau an show_retraction: - title: 'Rückzüge anzeigen' - subtitle: 'Zeige Filament-Rückzugsbewegungen in der Vorschau an' + title: Rückzüge anzeigen + subtitle: Zeige Filament-Rückzugsbewegungen in der Vorschau an show_travel: - title: 'Verfahrbewegungen anzeigen' - subtitle: 'Zeigt nicht-druckende Bewegungen in der Vorschau an' + title: Verfahrbewegungen anzeigen + subtitle: Zeigt nicht-druckende Bewegungen in der Vorschau an select_color_sheet: title: Farbe auswählen dialogs: diff --git a/assets/translations/hu.yaml b/assets/translations/hu.yaml index e81c91864..576272aca 100644 --- a/assets/translations/hu.yaml +++ b/assets/translations/hu.yaml @@ -62,6 +62,7 @@ general: discard: Eldobás hide: Elrejtés finish: Kész + select: Kiválaszt pages: dashboard: title: Irányítópult @@ -94,6 +95,7 @@ pages: Fájl: {file} Nyomtatószál: {filament} remaining: Fennmaradó + print_time: Nyomtatási idő cam_card: webcam: Webkamera fullscreen: Teljes képernyő @@ -927,6 +929,8 @@ components: show_travel: title: Utazási mozgások mutatása subtitle: Nem nyomtatási mozgás kijelzése + select_color_sheet: + title: Szín kiválasztása dialogs: rate_my_app: title: Értékeled a Mobileraker-t? @@ -966,7 +970,6 @@ dialogs: title: Archívum létrehozása label: Archívum neve delete_files: - title: Fájlok törlése description: A {} fájlok törlésének megerősítése. exclude_object: title: Objektum kizárása a nyomtatásból From e67d80bbd24b78c725388a158ef47e99e1d81fa1 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 3 Nov 2024 10:33:36 +0100 Subject: [PATCH 02/64] doc: Adds changelog entry for prev. fix --- docs/changelog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index cb7bc393a..b34675da8 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,12 @@ # Mobileraker - Changelog +## [2.8.3] - 2024-11-xx + +### Bug Fixes + +- **GCode-Preview**: Fixed an issue within the GCode-Parser that caused the preview to show an error if the + `SET_RETRACTION` was used in the STL file. + ## [2.8.2] - 2024-10-31 ### Bug Fixes From ffed2b32a65dcec318ddb49be0a1f6757951c0ab Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 3 Nov 2024 10:34:54 +0100 Subject: [PATCH 03/64] doc: Adjust wording in changelog --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index b34675da8..85ef5b637 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -5,7 +5,7 @@ ### Bug Fixes - **GCode-Preview**: Fixed an issue within the GCode-Parser that caused the preview to show an error if the - `SET_RETRACTION` was used in the STL file. + `SET_RETRACTION` was used in the GCode file. ## [2.8.2] - 2024-10-31 From f9fbce47685d66b399c3619ada273db75f8a7e78 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Fri, 8 Nov 2024 16:33:48 +0100 Subject: [PATCH 04/64] refactor: Minor improvement --- lib/service/ui/bottom_sheet_service_impl.dart | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/service/ui/bottom_sheet_service_impl.dart b/lib/service/ui/bottom_sheet_service_impl.dart index eb9bb44eb..cf6c5bb8c 100644 --- a/lib/service/ui/bottom_sheet_service_impl.dart +++ b/lib/service/ui/bottom_sheet_service_impl.dart @@ -60,20 +60,18 @@ class BottomSheetServiceImpl implements BottomSheetService { GoRoute( name: SheetType.nonPrintingMenu.name, path: '/sheet/non-printing', - pageBuilder: (context, state) { - return const DraggableNavigationSheetPage( - child: NonPrintingBottomSheet(), - ); - }, + pageBuilder: (context, state) => DraggableNavigationSheetPage( + key: state.pageKey, + child: NonPrintingBottomSheet(), + ), routes: [ GoRoute( name: SheetType.manageMachineServices.name, path: 'manage-services', - pageBuilder: (context, state) { - return const ScrollableNavigationSheetPage( - child: ManageServicesBottomSheet(), - ); - }, + pageBuilder: (context, state) => ScrollableNavigationSheetPage( + key: state.pageKey, + child: ManageServicesBottomSheet(), + ), ), ], ), @@ -85,6 +83,7 @@ class BottomSheetServiceImpl implements BottomSheetService { // SheetContentScaffold return DraggableNavigationSheetPage( + key: state.pageKey, name: state.name, child: ConfirmationBottomSheet(args: state.extra as ConfirmationBottomSheetArgs), ); @@ -96,6 +95,7 @@ class BottomSheetServiceImpl implements BottomSheetService { pageBuilder: (context, state) { // SheetContentScaffold return ScrollableNavigationSheetPage( + key: state.pageKey, name: state.name, child: const JobQueueBottomSheet(), ); @@ -108,6 +108,7 @@ class BottomSheetServiceImpl implements BottomSheetService { assert(state.extra is AddRemoteConnectionSheetArgs, 'Invalid extra data for AddRemoteConnectionSheetArgs'); // SheetContentScaffold return ScrollableNavigationSheetPage( + key: state.pageKey, name: state.name, child: AddRemoteConnectionBottomSheet(args: state.extra as AddRemoteConnectionSheetArgs), ); @@ -121,6 +122,7 @@ class BottomSheetServiceImpl implements BottomSheetService { 'Invalid extra data for ManageMacroGroupMacrosBottomSheetArguments'); // SheetContentScaffold return ScrollableNavigationSheetPage( + key: state.pageKey, name: state.name, child: ManageMacroGroupMacrosBottomSheet( arguments: state.extra as ManageMacroGroupMacrosBottomSheetArguments, @@ -133,6 +135,7 @@ class BottomSheetServiceImpl implements BottomSheetService { path: '/sheet/user-management', pageBuilder: (context, state) { return ScrollableNavigationSheetPage( + key: state.pageKey, name: state.name, child: const UserBottomSheet(), ); @@ -146,6 +149,7 @@ class BottomSheetServiceImpl implements BottomSheetService { // SheetContentScaffold return ScrollableNavigationSheetPage( + key: state.pageKey, name: state.name, child: SelectSpoolmanSheet(machineUUID: state.extra as String), ); @@ -159,6 +163,7 @@ class BottomSheetServiceImpl implements BottomSheetService { // SheetContentScaffold return ScrollableNavigationSheetPage( + key: state.pageKey, name: state.name, initialPosition: SheetAnchor.proportional(context.isCompact ? 0.6 : 1), child: DashboardCardsBottomSheet(machineUUID: state.extra as String), @@ -173,6 +178,7 @@ class BottomSheetServiceImpl implements BottomSheetService { // SheetContentScaffold return ScrollableNavigationSheetPage( + key: state.pageKey, name: state.name, child: DashboardLayoutBottomSheet( machineUUID: state.uri.queryParameters['machineUUID']!, @@ -189,6 +195,7 @@ class BottomSheetServiceImpl implements BottomSheetService { // ListView for padding handling return ScrollableNavigationSheetPage( + key: state.pageKey, name: state.name, child: SortModeBottomSheet(arguments: state.extra as SortModeSheetArgs), ); @@ -202,6 +209,7 @@ class BottomSheetServiceImpl implements BottomSheetService { // ListView for padding handling return ScrollableNavigationSheetPage( + key: state.pageKey, name: state.name, child: ActionBottomSheet(arguments: state.extra as ActionBottomSheetArgs), ); @@ -225,6 +233,7 @@ class BottomSheetServiceImpl implements BottomSheetService { path: '/sheet/gcode-visualizer-settings', pageBuilder: (context, state) { return ScrollableNavigationSheetPage( + key: state.pageKey, name: state.name, child: const GCodeVisualizerSettingsSheet(), ); @@ -238,6 +247,7 @@ class BottomSheetServiceImpl implements BottomSheetService { // SheetContentScaffold return ScrollableNavigationSheetPage( + key: state.pageKey, name: state.name, child: ColorPickerSheet(initialColor: state.extra as String?), ); From 32e315f27a434ca6a56357b07975dba16c4338ec Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Tue, 12 Nov 2024 21:24:44 +0100 Subject: [PATCH 05/64] fix: Fix #429 Namespace problem for different fan types The printer builder, object now include the identifier to ensure objects of same class (E.g. fans) with same name can co-exist --- .../lib/data/dto/machine/fans/named_fan.dart | 11 ++ .../filament_sensors/filament_sensor.dart | 10 ++ common/lib/data/dto/machine/leds/led.dart | 12 ++ .../lib/data/dto/machine/printer_builder.dart | 163 +++++------------- .../printer/printer_builder_test.dart | 5 +- 5 files changed, 83 insertions(+), 118 deletions(-) diff --git a/common/lib/data/dto/machine/fans/named_fan.dart b/common/lib/data/dto/machine/fans/named_fan.dart index 6b0f8d551..f29253319 100644 --- a/common/lib/data/dto/machine/fans/named_fan.dart +++ b/common/lib/data/dto/machine/fans/named_fan.dart @@ -7,6 +7,7 @@ import 'package:common/data/dto/machine/fans/controller_fan.dart'; import 'package:common/data/dto/machine/fans/generic_fan.dart'; import 'package:common/data/dto/machine/fans/temperature_fan.dart'; +import '../../config/config_file_object_identifiers_enum.dart'; import 'fan.dart'; import 'heater_fan.dart'; @@ -17,6 +18,16 @@ abstract class NamedFan implements Fan { String get configName => name.toLowerCase(); + factory NamedFan.fallback(ConfigFileObjectIdentifiers identifier, String name) { + return switch (identifier) { + ConfigFileObjectIdentifiers.heater_fan => HeaterFan(name: name), + ConfigFileObjectIdentifiers.controller_fan => ControllerFan(name: name), + ConfigFileObjectIdentifiers.temperature_fan => TemperatureFan(name: name, lastHistory: DateTime(1990)), + ConfigFileObjectIdentifiers.fan_generic => GenericFan(name: name), + _ => throw UnsupportedError('Unknown fan type: $identifier, can not create fallback.'), + }; + } + factory NamedFan.partialUpdate(NamedFan current, Map partialJson) { return switch (current) { HeaterFan() => HeaterFan.partialUpdate(current, partialJson), diff --git a/common/lib/data/dto/machine/filament_sensors/filament_sensor.dart b/common/lib/data/dto/machine/filament_sensors/filament_sensor.dart index 06f720adf..81ac0948e 100644 --- a/common/lib/data/dto/machine/filament_sensors/filament_sensor.dart +++ b/common/lib/data/dto/machine/filament_sensors/filament_sensor.dart @@ -6,11 +6,21 @@ import 'package:common/data/dto/machine/filament_sensors/filament_motion_sensor.dart'; import 'package:common/data/dto/machine/filament_sensors/filament_switch_sensor.dart'; +import '../../config/config_file_object_identifiers_enum.dart'; + abstract interface class FilamentSensor { abstract final String name; abstract final bool filamentDetected; abstract final bool enabled; + factory FilamentSensor.fallback(ConfigFileObjectIdentifiers identifier, String name) { + return switch (identifier) { + ConfigFileObjectIdentifiers.filament_motion_sensor => FilamentMotionSensor(name: name), + ConfigFileObjectIdentifiers.filament_switch_sensor => FilamentSwitchSensor(name: name), + _ => throw UnsupportedError('Unknown FilamentSensor type: $identifier, can not create fallback.'), + }; + } + factory FilamentSensor.partialUpdate(FilamentSensor current, Map partialJson) { if (current is FilamentMotionSensor) { return FilamentMotionSensor.partialUpdate(current, partialJson); diff --git a/common/lib/data/dto/machine/leds/led.dart b/common/lib/data/dto/machine/leds/led.dart index 6d2d66856..f6213375d 100644 --- a/common/lib/data/dto/machine/leds/led.dart +++ b/common/lib/data/dto/machine/leds/led.dart @@ -5,6 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import '../../config/config_file_object_identifiers_enum.dart'; import 'addressable_led.dart'; import 'dumb_led.dart'; @@ -54,6 +55,17 @@ abstract class Led { String get configName => name.toLowerCase(); + factory Led.fallback(ConfigFileObjectIdentifiers identifier, String name) { + return switch (identifier) { + ConfigFileObjectIdentifiers.dotstar || ConfigFileObjectIdentifiers.neopixel => AddressableLed(name: name), + ConfigFileObjectIdentifiers.led || + ConfigFileObjectIdentifiers.pca9533 || + ConfigFileObjectIdentifiers.pca9632 => + DumbLed(name: name), + _ => throw UnsupportedError('Unknown led type: $identifier, can not create fallback.'), + }; + } + factory Led.partialUpdate(Led current, Map partialJson) { if (current is DumbLed) { return DumbLed.partialUpdate(current, partialJson); diff --git a/common/lib/data/dto/machine/printer_builder.dart b/common/lib/data/dto/machine/printer_builder.dart index 42afeb18e..21b21f750 100644 --- a/common/lib/data/dto/machine/printer_builder.dart +++ b/common/lib/data/dto/machine/printer_builder.dart @@ -4,8 +4,6 @@ */ import 'package:common/data/dto/machine/bed_mesh/bed_mesh.dart'; -import 'package:common/data/dto/machine/fans/generic_fan.dart'; -import 'package:common/data/dto/machine/filament_sensors/filament_motion_sensor.dart'; import 'package:common/data/dto/machine/filament_sensors/filament_sensor.dart'; import 'package:common/data/dto/machine/gcode_macro.dart'; import 'package:common/data/dto/machine/print_stats.dart'; @@ -21,19 +19,13 @@ import '../files/gcode_file.dart'; import 'bed_screw.dart'; import 'display_status.dart'; import 'exclude_object.dart'; -import 'fans/controller_fan.dart'; -import 'fans/heater_fan.dart'; import 'fans/named_fan.dart'; import 'fans/print_fan.dart'; -import 'fans/temperature_fan.dart'; -import 'filament_sensors/filament_switch_sensor.dart'; import 'firmware_retraction.dart'; import 'gcode_move.dart'; import 'heaters/extruder.dart'; import 'heaters/generic_heater.dart'; import 'heaters/heater_bed.dart'; -import 'leds/addressable_led.dart'; -import 'leds/dumb_led.dart'; import 'leds/led.dart'; import 'manual_probe.dart'; import 'motion_report.dart'; @@ -47,37 +39,42 @@ final Map _subToPrinterObjects = { ConfigFileObjectIdentifiers.bed_mesh: PrinterBuilder._updateBedMesh, ConfigFileObjectIdentifiers.bed_screws: PrinterBuilder._updateBedScrew, ConfigFileObjectIdentifiers.configfile: PrinterBuilder._updateConfigFile, - ConfigFileObjectIdentifiers.controller_fan: PrinterBuilder._updateControllerFan, + ConfigFileObjectIdentifiers.controller_fan: PrinterBuilder._updateNamedFan, ConfigFileObjectIdentifiers.display_status: PrinterBuilder._updateDisplayStatus, - ConfigFileObjectIdentifiers.dotstar: PrinterBuilder._updateAddressableLed, + ConfigFileObjectIdentifiers.dotstar: PrinterBuilder._updateLed, ConfigFileObjectIdentifiers.exclude_object: PrinterBuilder._updateExcludeObject, ConfigFileObjectIdentifiers.extruder: PrinterBuilder._updateExtruder, ConfigFileObjectIdentifiers.fan: PrinterBuilder._updatePrintFan, - ConfigFileObjectIdentifiers.fan_generic: PrinterBuilder._updateGenericFan, - ConfigFileObjectIdentifiers.filament_motion_sensor: PrinterBuilder._updateFilamentMotionSensor, - ConfigFileObjectIdentifiers.filament_switch_sensor: PrinterBuilder._updateFilamentSwitchSensor, + ConfigFileObjectIdentifiers.fan_generic: PrinterBuilder._updateNamedFan, + ConfigFileObjectIdentifiers.filament_motion_sensor: PrinterBuilder._updateFilamentSensor, + ConfigFileObjectIdentifiers.filament_switch_sensor: PrinterBuilder._updateFilamentSensor, ConfigFileObjectIdentifiers.firmware_retraction: PrinterBuilder._updateFirmwareRetraction, ConfigFileObjectIdentifiers.gcode_macro: PrinterBuilder._updateGcodeMacro, ConfigFileObjectIdentifiers.gcode_move: PrinterBuilder._updateGCodeMove, ConfigFileObjectIdentifiers.heater_bed: PrinterBuilder._updateHeaterBed, - ConfigFileObjectIdentifiers.heater_fan: PrinterBuilder._updateHeaterFan, + ConfigFileObjectIdentifiers.heater_fan: PrinterBuilder._updateNamedFan, ConfigFileObjectIdentifiers.heater_generic: PrinterBuilder._updateGenericHeater, - ConfigFileObjectIdentifiers.led: PrinterBuilder._updateDumbLed, + ConfigFileObjectIdentifiers.led: PrinterBuilder._updateLed, ConfigFileObjectIdentifiers.manual_probe: PrinterBuilder._updateManualProbe, ConfigFileObjectIdentifiers.motion_report: PrinterBuilder._updateMotionReport, - ConfigFileObjectIdentifiers.neopixel: PrinterBuilder._updateAddressableLed, + ConfigFileObjectIdentifiers.neopixel: PrinterBuilder._updateLed, ConfigFileObjectIdentifiers.output_pin: PrinterBuilder._updateOutputPin, - ConfigFileObjectIdentifiers.pca9533: PrinterBuilder._updateDumbLed, - ConfigFileObjectIdentifiers.pca9632: PrinterBuilder._updateDumbLed, + ConfigFileObjectIdentifiers.pca9533: PrinterBuilder._updateLed, + ConfigFileObjectIdentifiers.pca9632: PrinterBuilder._updateLed, ConfigFileObjectIdentifiers.print_stats: PrinterBuilder._updatePrintStat, ConfigFileObjectIdentifiers.screws_tilt_adjust: PrinterBuilder._updateScrewsTiltAdjust, - ConfigFileObjectIdentifiers.temperature_fan: PrinterBuilder._updateTemperatureFan, + ConfigFileObjectIdentifiers.temperature_fan: PrinterBuilder._updateNamedFan, ConfigFileObjectIdentifiers.temperature_sensor: PrinterBuilder._updateTemperatureSensor, ConfigFileObjectIdentifiers.toolhead: PrinterBuilder._updateToolhead, ConfigFileObjectIdentifiers.virtual_sdcard: PrinterBuilder._updateVirtualSd, ConfigFileObjectIdentifiers.z_thermal_adjust: PrinterBuilder._updateZThermalAdjust, }; +typedef _SingleObjectUpdate = PrinterBuilder Function(Map, PrinterBuilder); +typedef _MultiObjectUpdate = PrinterBuilder Function(String, Map, PrinterBuilder); +typedef _MultiObjectWithIdentifierUpdate = PrinterBuilder Function( + ConfigFileObjectIdentifiers, String, Map, PrinterBuilder); + class PrinterBuilder { PrinterBuilder(); @@ -157,7 +154,6 @@ class PrinterBuilder { if (toolhead == null) { throw const MobilerakerException('Missing field: toolhead'); } - if (gCodeMove == null) { throw const MobilerakerException('Missing field: gCodeMove'); } @@ -214,17 +210,21 @@ class PrinterBuilder { final updateMethodToCall = _subToPrinterObjects[cIdentifier]; if (updateMethodToCall == null) return this; // - // No method to update the object -> skip - if (objectName != null) { - updateMethodToCall(objectName, json[key], this); - } else if (cIdentifier == ConfigFileObjectIdentifiers.extruder) { - // Extruder is a special case.... - updateMethodToCall(key, json[key], this); - } else { - updateMethodToCall(json[key], this); - } - return this; + // if (updateMethodToCall case _MultiObjectUpdate()) { + // return this; + // } + + return switch (updateMethodToCall) { + _SingleObjectUpdate() => updateMethodToCall(json[key], this), + // Extruder is a special case.... + _MultiObjectUpdate() when cIdentifier == ConfigFileObjectIdentifiers.extruder => + updateMethodToCall(key, json[key], this), + _MultiObjectUpdate() when objectName != null => updateMethodToCall(objectName, json[key], this), + _MultiObjectWithIdentifierUpdate() when objectName != null => + updateMethodToCall(cIdentifier, objectName, json[key], this), + _ => throw UnsupportedError('The provided update method is not implemented yet!') + }; } //////////////////////////////// @@ -250,30 +250,25 @@ class PrinterBuilder { return builder..configFile = config; } - static PrinterBuilder _updateControllerFan(String fanName, Map fanJson, PrinterBuilder builder) { - final curFan = builder.fans[fanName] ?? ControllerFan(name: fanName); - - if (curFan is! ControllerFan) { - logger.w('Fan "$fanName" is not a ControllerFan'); - throw MobilerakerException('Fan "$fanName" is not a ControllerFan. Found ${_typeOrNull(curFan)}'); - } + static PrinterBuilder _updateNamedFan( + ConfigFileObjectIdentifiers identifier, String name, Map fanJson, PrinterBuilder builder) { + // We need to combine identifier and name again because fans can have the same name as long as they are not the same type causing issues here! + final key = '${identifier.name}::$name'; + final curFan = builder.fans[key] ?? NamedFan.fallback(identifier, name); - return builder..fans = {...builder.fans, fanName: ControllerFan.partialUpdate(curFan, fanJson)}; + return builder..fans = {...builder.fans, key: NamedFan.partialUpdate(curFan, fanJson)}; } static PrinterBuilder _updateDisplayStatus(Map json, PrinterBuilder builder) { return builder..displayStatus = DisplayStatus.partialUpdate(builder.displayStatus, json); } - static PrinterBuilder _updateAddressableLed(String led, Map json, PrinterBuilder builder) { - final curLed = builder.leds[led] ?? AddressableLed(name: led); - - if (curLed is! AddressableLed) { - logger.w('Led "$led" is not an AddressableLed'); - throw MobilerakerException('Led "$led" is not an AddressableLed. Found ${_typeOrNull(led)}'); - } + static PrinterBuilder _updateLed( + ConfigFileObjectIdentifiers identifier, String name, Map json, PrinterBuilder builder) { + final key = '${identifier.name}::$name'; + final curLed = builder.leds[key] ?? Led.fallback(identifier, name); - return builder..leds = {...builder.leds, led: AddressableLed.partialUpdate(curLed, json)}; + return builder..leds = {...builder.leds, key: Led.partialUpdate(curLed, json)}; } static PrinterBuilder _updateExcludeObject(Map json, PrinterBuilder builder) { @@ -303,44 +298,13 @@ class PrinterBuilder { return builder..printFan = PrintFan.partialUpdate(builder.printFan, json); } - static PrinterBuilder _updateGenericFan(String fanName, Map fanJson, PrinterBuilder builder) { - final curFan = builder.fans[fanName] ?? GenericFan(name: fanName); - - if (curFan is! GenericFan) { - logger.w('Fan "$fanName" is not a GenericFan'); - throw MobilerakerException('Fan "$fanName" is not a GenericFan. Found ${_typeOrNull(curFan)}'); - } - - return builder..fans = {...builder.fans, fanName: GenericFan.partialUpdate(curFan, fanJson)}; - } - - static PrinterBuilder _updateFilamentMotionSensor(String sensor, Map json, PrinterBuilder builder) { - final filamentSensor = builder.filamentSensors[sensor] ?? FilamentMotionSensor(name: sensor); - - if (filamentSensor is! FilamentMotionSensor) { - logger.w('Sensor "$sensor" is not a FilamentMotionSensor'); - throw MobilerakerException('Sensor "$sensor" is not a FilamentMotionSensor. Found ${_typeOrNull(sensor)}'); - } + static PrinterBuilder _updateFilamentSensor( + ConfigFileObjectIdentifiers identifier, String name, Map json, PrinterBuilder builder) { + final key = '${identifier.name}::$name'; + final filamentSensor = builder.filamentSensors[key] ?? FilamentSensor.fallback(identifier, name); return builder - ..filamentSensors = { - ...builder.filamentSensors, - sensor: FilamentMotionSensor.partialUpdate(filamentSensor, json) - }; - } - - static PrinterBuilder _updateFilamentSwitchSensor(String sensor, Map json, PrinterBuilder builder) { - final filamentSensor = builder.filamentSensors[sensor] ?? FilamentSwitchSensor(name: sensor); - if (filamentSensor is! FilamentSwitchSensor) { - logger.w('Sensor "$sensor" is not a FilamentSwitchSensor'); - throw MobilerakerException('Sensor "$sensor" is not a FilamentSwitchSensor. Found ${_typeOrNull(sensor)}'); - } - - return builder - ..filamentSensors = { - ...builder.filamentSensors, - sensor: FilamentSwitchSensor.partialUpdate(filamentSensor, json) - }; + ..filamentSensors = {...builder.filamentSensors, key: FilamentSensor.partialUpdate(filamentSensor, json)}; } static PrinterBuilder _updateFirmwareRetraction(Map json, PrinterBuilder builder) { @@ -360,34 +324,12 @@ class PrinterBuilder { return builder..heaterBed = HeaterBed.partialUpdate(builder.heaterBed, json); } - static PrinterBuilder _updateHeaterFan(String fanName, Map fanJson, PrinterBuilder builder) { - final curFan = builder.fans[fanName] ?? HeaterFan(name: fanName); - - if (curFan is! HeaterFan) { - logger.w('Fan "$fanName" is not a HeaterFan'); - throw MobilerakerException('Fan "$fanName" is not a HeaterFan. Found ${_typeOrNull(curFan)}'); - } - - return builder..fans = {...builder.fans, fanName: HeaterFan.partialUpdate(curFan, fanJson)}; - } - static PrinterBuilder _updateGenericHeater(String heater, Map json, PrinterBuilder builder) { final genericHeater = builder.genericHeaters[heater] ?? GenericHeater(name: heater, lastHistory: DateTime(1990)); return builder ..genericHeaters = {...builder.genericHeaters, heater: GenericHeater.partialUpdate(genericHeater, json)}; } - static PrinterBuilder _updateDumbLed(String led, Map json, PrinterBuilder builder) { - final curLed = builder.leds[led] ?? DumbLed(name: led); - - if (curLed is! DumbLed) { - logger.w('Led "$led" is not an DumbLed'); - throw MobilerakerException('Led "$led" is not an DumbLed. Found ${_typeOrNull(led)}'); - } - - return builder..leds = {...builder.leds, led: DumbLed.partialUpdate(curLed, json)}; - } - static PrinterBuilder _updateManualProbe(Map json, PrinterBuilder builder) { return builder..manualProbe = ManualProbe.partialUpdate(builder.manualProbe, json); } @@ -409,17 +351,6 @@ class PrinterBuilder { return builder..screwsTiltAdjust = ScrewsTiltAdjust.partialUpdate(builder.screwsTiltAdjust, json); } - static PrinterBuilder _updateTemperatureFan(String fanName, Map fanJson, PrinterBuilder builder) { - final curFan = builder.fans[fanName] ?? TemperatureFan(name: fanName, lastHistory: DateTime(1990)); - - if (curFan is! TemperatureFan) { - logger.w('Fan "$fanName" is not a TemperatureFan'); - throw MobilerakerException('Fan "$fanName" is not a TemperatureFan. Found ${_typeOrNull(curFan)}'); - } - - return builder..fans = {...builder.fans, fanName: TemperatureFan.partialUpdate(curFan, fanJson)}; - } - static PrinterBuilder _updateTemperatureSensor(String sensor, Map json, PrinterBuilder builder) { final temperatureSensor = builder.temperatureSensors[sensor] ?? TemperatureSensor(name: sensor, lastHistory: DateTime(1990)); @@ -448,4 +379,4 @@ class PrinterBuilder { //////////////////////////////// } -String _typeOrNull(dynamic obj) => obj == null ? 'null' : obj.runtimeType.toString(); \ No newline at end of file +String _typeOrNull(dynamic obj) => obj == null ? 'null' : obj.runtimeType.toString(); diff --git a/common/test/unit/marshalling/printer/printer_builder_test.dart b/common/test/unit/marshalling/printer/printer_builder_test.dart index 78df35ec6..4d8f00929 100644 --- a/common/test/unit/marshalling/printer/printer_builder_test.dart +++ b/common/test/unit/marshalling/printer/printer_builder_test.dart @@ -188,13 +188,14 @@ void main() { test('Update generic fan', () { final builder = PrinterBuilder.preview(); - builder.fans['hotend_fan'] = GenericFan(name: 'hotend_fan'); + final key = 'fan_generic::hotend_fan'; + builder.fans[key] = GenericFan(name: 'hotend_fan'); final json = { 'fan_generic hotend_fan': {'speed': 100} }; final updatedBuilder = builder.partialUpdateField('fan_generic hotend_fan', json); - expect(updatedBuilder.fans['hotend_fan']!.speed, 100); + expect(updatedBuilder.fans[key]!.speed, 100); }); }); } From 239481e552377e78deaf1e576b31a8e778f05423 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Tue, 12 Nov 2024 21:36:19 +0100 Subject: [PATCH 06/64] fix: Fix #429 Namespace problem for different fan types The printer builder, object now include the identifier to ensure objects of same class (E.g. fans) with same name can co-exist --- common/lib/data/dto/machine/fans/controller_fan.dart | 4 ++++ common/lib/data/dto/machine/fans/fan.dart | 4 ++++ common/lib/data/dto/machine/fans/generic_fan.dart | 4 ++++ common/lib/data/dto/machine/fans/heater_fan.dart | 4 ++++ common/lib/data/dto/machine/fans/print_fan.dart | 4 ++++ .../lib/data/dto/machine/fans/temperature_fan.dart | 4 ++++ .../filament_sensors/filament_motion_sensor.dart | 7 +++++++ .../machine/filament_sensors/filament_sensor.dart | 2 ++ .../filament_sensors/filament_switch_sensor.dart | 6 ++++++ .../lib/data/dto/machine/leds/addressable_led.dart | 2 ++ common/lib/data/dto/machine/leds/dumb_led.dart | 2 ++ common/lib/data/dto/machine/leds/led.dart | 8 ++++++-- lib/ui/screens/dashboard/components/fans_card.dart | 2 +- lib/ui/screens/dashboard/components/pins_card.dart | 3 ++- .../printers/edit/components/fans_ordering_list.dart | 12 +----------- 15 files changed, 53 insertions(+), 15 deletions(-) diff --git a/common/lib/data/dto/machine/fans/controller_fan.dart b/common/lib/data/dto/machine/fans/controller_fan.dart index b4414a6dc..eec85849f 100644 --- a/common/lib/data/dto/machine/fans/controller_fan.dart +++ b/common/lib/data/dto/machine/fans/controller_fan.dart @@ -3,6 +3,7 @@ * All rights reserved. */ +import 'package:common/data/dto/config/config_file_object_identifiers_enum.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'named_fan.dart'; @@ -27,4 +28,7 @@ class ControllerFan extends NamedFan with _$ControllerFan { var mergedJson = {...current.toJson(), ...partialJson}; return ControllerFan.fromJson(mergedJson); } + + @override + ConfigFileObjectIdentifiers get kind => ConfigFileObjectIdentifiers.controller_fan; } diff --git a/common/lib/data/dto/machine/fans/fan.dart b/common/lib/data/dto/machine/fans/fan.dart index 12f074af6..4778f2dee 100644 --- a/common/lib/data/dto/machine/fans/fan.dart +++ b/common/lib/data/dto/machine/fans/fan.dart @@ -3,7 +3,11 @@ * All rights reserved. */ +import '../../config/config_file_object_identifiers_enum.dart'; + abstract class Fan { abstract final double speed; abstract final double? rpm; + + ConfigFileObjectIdentifiers get kind; } diff --git a/common/lib/data/dto/machine/fans/generic_fan.dart b/common/lib/data/dto/machine/fans/generic_fan.dart index fbc0be870..578e7116c 100644 --- a/common/lib/data/dto/machine/fans/generic_fan.dart +++ b/common/lib/data/dto/machine/fans/generic_fan.dart @@ -5,6 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import '../../config/config_file_object_identifiers_enum.dart'; import 'named_fan.dart'; part 'generic_fan.freezed.dart'; @@ -27,4 +28,7 @@ class GenericFan extends NamedFan with _$GenericFan { var mergedJson = {...current.toJson(), ...partialJson}; return GenericFan.fromJson(mergedJson); } + + @override + ConfigFileObjectIdentifiers get kind => ConfigFileObjectIdentifiers.fan_generic; } diff --git a/common/lib/data/dto/machine/fans/heater_fan.dart b/common/lib/data/dto/machine/fans/heater_fan.dart index ac9b5e4a0..df315473c 100644 --- a/common/lib/data/dto/machine/fans/heater_fan.dart +++ b/common/lib/data/dto/machine/fans/heater_fan.dart @@ -5,6 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import '../../config/config_file_object_identifiers_enum.dart'; import 'named_fan.dart'; part 'heater_fan.freezed.dart'; @@ -27,4 +28,7 @@ class HeaterFan extends NamedFan with _$HeaterFan { var mergedJson = {...current.toJson(), ...partialJson}; return HeaterFan.fromJson(mergedJson); } + + @override + ConfigFileObjectIdentifiers get kind => ConfigFileObjectIdentifiers.heater_fan; } diff --git a/common/lib/data/dto/machine/fans/print_fan.dart b/common/lib/data/dto/machine/fans/print_fan.dart index cef7c7c7e..1f3072ea8 100644 --- a/common/lib/data/dto/machine/fans/print_fan.dart +++ b/common/lib/data/dto/machine/fans/print_fan.dart @@ -5,6 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import '../../config/config_file_object_identifiers_enum.dart'; import 'fan.dart'; part 'print_fan.freezed.dart'; @@ -27,4 +28,7 @@ class PrintFan with _$PrintFan implements Fan { var mergedJson = {...old.toJson(), ...partialJson}; return PrintFan.fromJson(mergedJson); } + + @override + ConfigFileObjectIdentifiers get kind => ConfigFileObjectIdentifiers.fan; } diff --git a/common/lib/data/dto/machine/fans/temperature_fan.dart b/common/lib/data/dto/machine/fans/temperature_fan.dart index be2dc3d4b..2d6fcc4d4 100644 --- a/common/lib/data/dto/machine/fans/temperature_fan.dart +++ b/common/lib/data/dto/machine/fans/temperature_fan.dart @@ -6,6 +6,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import '../../../../util/json_util.dart'; +import '../../config/config_file_object_identifiers_enum.dart'; import '../sensor_mixin.dart'; import 'named_fan.dart'; @@ -53,4 +54,7 @@ class TemperatureFan extends NamedFan with _$TemperatureFan, SensorMixin { } return TemperatureFan.fromJson(mergedJson); } + + @override + ConfigFileObjectIdentifiers get kind => ConfigFileObjectIdentifiers.temperature_fan; } diff --git a/common/lib/data/dto/machine/filament_sensors/filament_motion_sensor.dart b/common/lib/data/dto/machine/filament_sensors/filament_motion_sensor.dart index 32e75efaf..6cd60b775 100644 --- a/common/lib/data/dto/machine/filament_sensors/filament_motion_sensor.dart +++ b/common/lib/data/dto/machine/filament_sensors/filament_motion_sensor.dart @@ -6,6 +6,8 @@ import 'package:common/data/dto/machine/filament_sensors/filament_sensor.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import '../../config/config_file_object_identifiers_enum.dart'; + part 'filament_motion_sensor.freezed.dart'; part 'filament_motion_sensor.g.dart'; @@ -20,6 +22,8 @@ part 'filament_motion_sensor.g.dart'; @freezed class FilamentMotionSensor with _$FilamentMotionSensor implements FilamentSensor { + const FilamentMotionSensor._(); + @JsonSerializable(fieldRename: FieldRename.snake) const factory FilamentMotionSensor({ required String name, @@ -34,4 +38,7 @@ class FilamentMotionSensor with _$FilamentMotionSensor implements FilamentSensor var mergedJson = {...current.toJson(), ...partialJson}; return FilamentMotionSensor.fromJson(mergedJson); } + + @override + ConfigFileObjectIdentifiers get kind => ConfigFileObjectIdentifiers.filament_motion_sensor; } diff --git a/common/lib/data/dto/machine/filament_sensors/filament_sensor.dart b/common/lib/data/dto/machine/filament_sensors/filament_sensor.dart index 81ac0948e..4cc77c78e 100644 --- a/common/lib/data/dto/machine/filament_sensors/filament_sensor.dart +++ b/common/lib/data/dto/machine/filament_sensors/filament_sensor.dart @@ -13,6 +13,8 @@ abstract interface class FilamentSensor { abstract final bool filamentDetected; abstract final bool enabled; + ConfigFileObjectIdentifiers get kind; + factory FilamentSensor.fallback(ConfigFileObjectIdentifiers identifier, String name) { return switch (identifier) { ConfigFileObjectIdentifiers.filament_motion_sensor => FilamentMotionSensor(name: name), diff --git a/common/lib/data/dto/machine/filament_sensors/filament_switch_sensor.dart b/common/lib/data/dto/machine/filament_sensors/filament_switch_sensor.dart index 651859c5e..59165e619 100644 --- a/common/lib/data/dto/machine/filament_sensors/filament_switch_sensor.dart +++ b/common/lib/data/dto/machine/filament_sensors/filament_switch_sensor.dart @@ -5,6 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import '../../config/config_file_object_identifiers_enum.dart'; import 'filament_sensor.dart'; part 'filament_switch_sensor.freezed.dart'; @@ -21,6 +22,8 @@ part 'filament_switch_sensor.g.dart'; @freezed class FilamentSwitchSensor with _$FilamentSwitchSensor implements FilamentSensor { + const FilamentSwitchSensor._(); + @JsonSerializable(fieldRename: FieldRename.snake) const factory FilamentSwitchSensor({ required String name, @@ -35,4 +38,7 @@ class FilamentSwitchSensor with _$FilamentSwitchSensor implements FilamentSensor var mergedJson = {...current.toJson(), ...partialJson}; return FilamentSwitchSensor.fromJson(mergedJson); } + + @override + ConfigFileObjectIdentifiers get kind => ConfigFileObjectIdentifiers.filament_switch_sensor; } diff --git a/common/lib/data/dto/machine/leds/addressable_led.dart b/common/lib/data/dto/machine/leds/addressable_led.dart index 704f8e45c..20ada7c76 100644 --- a/common/lib/data/dto/machine/leds/addressable_led.dart +++ b/common/lib/data/dto/machine/leds/addressable_led.dart @@ -6,6 +6,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import '../../../converters/pixel_converter.dart'; +import '../../config/config_file_object_identifiers_enum.dart'; import 'led.dart'; part 'addressable_led.freezed.dart'; @@ -16,6 +17,7 @@ class AddressableLed extends Led with _$AddressableLed { const AddressableLed._(); const factory AddressableLed({ required String name, + required ConfigFileObjectIdentifiers kind, @PixelConverter() @JsonKey(name: 'color_data') @Default([]) List pixels, }) = _AddressableLed; diff --git a/common/lib/data/dto/machine/leds/dumb_led.dart b/common/lib/data/dto/machine/leds/dumb_led.dart index 81954a2f9..ef1e64f28 100644 --- a/common/lib/data/dto/machine/leds/dumb_led.dart +++ b/common/lib/data/dto/machine/leds/dumb_led.dart @@ -6,6 +6,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import '../../../converters/pixel_converter.dart'; +import '../../config/config_file_object_identifiers_enum.dart'; import 'led.dart'; part 'dumb_led.freezed.dart'; @@ -17,6 +18,7 @@ class DumbLed extends Led with _$DumbLed { const factory DumbLed({ required String name, + required ConfigFileObjectIdentifiers kind, @PixelConverter() @JsonKey(name: 'color_data', readValue: _extractFirstLed) @Default(Pixel()) Pixel color, }) = _DumbLed; diff --git a/common/lib/data/dto/machine/leds/led.dart b/common/lib/data/dto/machine/leds/led.dart index f6213375d..354dcd898 100644 --- a/common/lib/data/dto/machine/leds/led.dart +++ b/common/lib/data/dto/machine/leds/led.dart @@ -55,13 +55,17 @@ abstract class Led { String get configName => name.toLowerCase(); + ConfigFileObjectIdentifiers get kind; + factory Led.fallback(ConfigFileObjectIdentifiers identifier, String name) { return switch (identifier) { - ConfigFileObjectIdentifiers.dotstar || ConfigFileObjectIdentifiers.neopixel => AddressableLed(name: name), + ConfigFileObjectIdentifiers.dotstar || + ConfigFileObjectIdentifiers.neopixel => + AddressableLed(name: name, kind: identifier), ConfigFileObjectIdentifiers.led || ConfigFileObjectIdentifiers.pca9533 || ConfigFileObjectIdentifiers.pca9632 => - DumbLed(name: name), + DumbLed(name: name, kind: identifier), _ => throw UnsupportedError('Unknown led type: $identifier, can not create fallback.'), }; } diff --git a/lib/ui/screens/dashboard/components/fans_card.dart b/lib/ui/screens/dashboard/components/fans_card.dart index 6de10c74a..addecfaa8 100644 --- a/lib/ui/screens/dashboard/components/fans_card.dart +++ b/lib/ui/screens/dashboard/components/fans_card.dart @@ -302,7 +302,7 @@ class _FansCardController extends _$FansCardController { int getOrderingIndex(Fan fan) { return ordering.indexWhere((element) { return switch (fan) { - NamedFan() => element.name == fan.name, + NamedFan() => element.name == fan.name && element.kind == fan.kind, PrintFan() => element.kind == ConfigFileObjectIdentifiers.fan, _ => false }; diff --git a/lib/ui/screens/dashboard/components/pins_card.dart b/lib/ui/screens/dashboard/components/pins_card.dart index fa75cc3f8..5201e04b6 100644 --- a/lib/ui/screens/dashboard/components/pins_card.dart +++ b/lib/ui/screens/dashboard/components/pins_card.dart @@ -6,6 +6,7 @@ import 'dart:math'; import 'package:auto_size_text/auto_size_text.dart'; +import 'package:common/data/dto/config/config_file_object_identifiers_enum.dart'; import 'package:common/data/dto/config/config_output.dart'; import 'package:common/data/dto/config/led/config_dumb_led.dart'; import 'package:common/data/dto/config/led/config_led.dart'; @@ -663,7 +664,7 @@ class _PinsCardPreviewController extends _PinsCardController { klippyCanReceiveCommands: true, elements: [ OutputPin(name: 'Preview Pin', value: 0), - DumbLed(name: 'Preview Led'), + DumbLed(name: 'Preview Led', kind: ConfigFileObjectIdentifiers.led), ], ledConfig: { 'preview led': ConfigDumbLed( diff --git a/lib/ui/screens/printers/edit/components/fans_ordering_list.dart b/lib/ui/screens/printers/edit/components/fans_ordering_list.dart index 599ccf9f0..224e3f4f1 100644 --- a/lib/ui/screens/printers/edit/components/fans_ordering_list.dart +++ b/lib/ui/screens/printers/edit/components/fans_ordering_list.dart @@ -7,9 +7,6 @@ import 'dart:ui'; import 'package:collection/collection.dart'; import 'package:common/data/dto/config/config_file_object_identifiers_enum.dart'; -import 'package:common/data/dto/machine/fans/controller_fan.dart'; -import 'package:common/data/dto/machine/fans/heater_fan.dart'; -import 'package:common/data/dto/machine/fans/temperature_fan.dart'; import 'package:common/data/dto/machine/printer.dart'; import 'package:common/data/model/moonraker_db/settings/reordable_element.dart'; import 'package:common/service/machine_service.dart'; @@ -143,14 +140,7 @@ class FansOrderingListController extends _$FansOrderingListController { } for (var fan in printerData.fans.values) { - var kind = switch (fan) { - HeaterFan() => ConfigFileObjectIdentifiers.heater_fan, - TemperatureFan() => ConfigFileObjectIdentifiers.temperature_fan, - ControllerFan() => ConfigFileObjectIdentifiers.controller_fan, - _ => ConfigFileObjectIdentifiers.fan_generic, - }; - - availableElements.add(ReordableElement(name: fan.name, kind: kind)); + availableElements.add(ReordableElement(name: fan.name, kind: fan.kind)); } return availableElements; From ae85eabc7ee5eb8e04eba967c17475a617a95d41 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Tue, 12 Nov 2024 21:50:59 +0100 Subject: [PATCH 07/64] test: Adjust test to honor new kind type --- .../printer/addressable_led_marshalling_test.dart | 6 +++--- .../unit/marshalling/printer/dumb_led_marshalling_test.dart | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/test/unit/marshalling/printer/addressable_led_marshalling_test.dart b/common/test/unit/marshalling/printer/addressable_led_marshalling_test.dart index 0b46fe941..2f23bb66c 100644 --- a/common/test/unit/marshalling/printer/addressable_led_marshalling_test.dart +++ b/common/test/unit/marshalling/printer/addressable_led_marshalling_test.dart @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023. Patrick Schmidt. + * Copyright (c) 2023-2024. Patrick Schmidt. * All rights reserved. */ @@ -90,7 +90,7 @@ AddressableLed addressableLedObject() { var jsonRaw = objectFromHttpApiResult(input, 'neopixel sb_leds'); - return AddressableLed.fromJson(jsonRaw, 'sb_leds'); + return AddressableLed.fromJson({...jsonRaw, 'kind': 'neopixel'}, 'sb_leds'); } AddressableLed legacyAddressableLedObject() { @@ -99,5 +99,5 @@ AddressableLed legacyAddressableLedObject() { var jsonRaw = objectFromHttpApiResult(input, 'neopixel sb_leds'); - return AddressableLed.fromJson(jsonRaw, 'sb_leds'); + return AddressableLed.fromJson({...jsonRaw, 'kind': 'neopixel'}, 'sb_leds'); } diff --git a/common/test/unit/marshalling/printer/dumb_led_marshalling_test.dart b/common/test/unit/marshalling/printer/dumb_led_marshalling_test.dart index 56db5daad..86c4723f5 100644 --- a/common/test/unit/marshalling/printer/dumb_led_marshalling_test.dart +++ b/common/test/unit/marshalling/printer/dumb_led_marshalling_test.dart @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023. Patrick Schmidt. + * Copyright (c) 2023-2024. Patrick Schmidt. * All rights reserved. */ @@ -56,5 +56,5 @@ DumbLed dumbLedObject() { var jsonRaw = objectFromHttpApiResult(input, 'led caselight'); - return DumbLed.fromJson(jsonRaw, 'caselight'); + return DumbLed.fromJson({...jsonRaw, 'kind': 'led'}, 'caselight'); } From ecfcb290fdff8afe444b3415ce8a0518500139e5 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Tue, 12 Nov 2024 22:16:45 +0100 Subject: [PATCH 08/64] chore: Configfile now also supports multi named objects --- common/lib/data/dto/config/config_file.dart | 68 ++++++++--------- .../lib/data/dto/machine/printer_builder.dart | 4 +- .../lib/util/extensions/string_extension.dart | 16 ---- .../configfile_marshalling_test.dart | 17 +++-- .../printer/extruder_marshalling_test.dart | 8 +- .../test/unit/util/string_extension_test.dart | 76 ++----------------- .../dashboard/components/pins_card.dart | 10 +-- .../temperature_card/heater_sensor_card.dart | 4 +- 8 files changed, 60 insertions(+), 143 deletions(-) diff --git a/common/lib/data/dto/config/config_file.dart b/common/lib/data/dto/config/config_file.dart index a23e28dae..5c81a705f 100644 --- a/common/lib/data/dto/config/config_file.dart +++ b/common/lib/data/dto/config/config_file.dart @@ -41,8 +41,8 @@ class ConfigFile { Map outputs = {}; Map steppers = {}; Map gcodeMacros = {}; - Map leds = {}; - Map fans = {}; + Map<(ConfigFileObjectIdentifiers, String), ConfigLed> leds = {}; + Map<(ConfigFileObjectIdentifiers, String), ConfigFan> fans = {}; Map genericHeaters = {}; ConfigFile(); @@ -53,56 +53,54 @@ class ConfigFile { ConfigFile.parse(this.rawConfig) { for (String key in rawConfig.keys) { - var klipperObjectIdentifier = key.toKlipperObjectIdentifier(); - String objectIdentifier = klipperObjectIdentifier.$1; - String objectName = klipperObjectIdentifier.$2 ?? klipperObjectIdentifier.$1; + var (cIdentifier, objectName) = key.toKlipperObjectIdentifierNEW(); Map jsonChild = Map.of(rawConfig[key]); - if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.heater_bed)) { + if (cIdentifier == ConfigFileObjectIdentifiers.heater_bed) { configHeaterBed = ConfigHeaterBed.fromJson(rawConfig['heater_bed']); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.printer)) { + } else if (cIdentifier == ConfigFileObjectIdentifiers.printer) { configPrinter = ConfigPrinter.fromJson(rawConfig['printer']); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.extruder)) { + } else if (cIdentifier == ConfigFileObjectIdentifiers.extruder) { if (jsonChild.containsKey('shared_heater')) { String sharedHeater = jsonChild['shared_heater']; Map sharedHeaterConfig = Map.of(rawConfig[sharedHeater]); sharedHeaterConfig.removeWhere((key, value) => jsonChild.containsKey(key)); jsonChild.addAll(sharedHeaterConfig); } - extruders[objectIdentifier] = ConfigExtruder.fromJson(objectIdentifier, jsonChild); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.output_pin)) { - outputs[objectName] = ConfigOutput.fromJson(objectName, jsonChild); + extruders[key] = ConfigExtruder.fromJson(key, jsonChild); + } else if (cIdentifier == ConfigFileObjectIdentifiers.output_pin) { + outputs[objectName!] = ConfigOutput.fromJson(objectName, jsonChild); } else if (stepperRegex.hasMatch(key)) { var match = stepperRegex.firstMatch(key)!; steppers[match.group(1)!] = ConfigStepper.fromJson(match.group(1)!, jsonChild); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.gcode_macro)) { - gcodeMacros[objectName] = ConfigGcodeMacro.fromJson(objectName, jsonChild); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.dotstar)) { - leds[objectName] = ConfigDotstar.fromJson(objectName, jsonChild); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.neopixel)) { - leds[objectName] = ConfigNeopixel.fromJson(objectName, jsonChild); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.led)) { - leds[objectName] = ConfigDumbLed.fromJson(objectName, jsonChild); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.pca9533) || - objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.pca9632)) { + } else if (cIdentifier == ConfigFileObjectIdentifiers.gcode_macro) { + gcodeMacros[objectName!] = ConfigGcodeMacro.fromJson(objectName, jsonChild); + } else if (cIdentifier == ConfigFileObjectIdentifiers.dotstar) { + leds[(cIdentifier!, objectName!)] = ConfigDotstar.fromJson(objectName, jsonChild); + } else if (cIdentifier == ConfigFileObjectIdentifiers.neopixel) { + leds[(cIdentifier!, objectName!)] = ConfigNeopixel.fromJson(objectName, jsonChild); + } else if (cIdentifier == ConfigFileObjectIdentifiers.led) { + leds[(cIdentifier!, objectName!)] = ConfigDumbLed.fromJson(objectName, jsonChild); + } else if (cIdentifier == ConfigFileObjectIdentifiers.pca9533 || + cIdentifier == ConfigFileObjectIdentifiers.pca9632) { //pca9533 and pcapca9632 - leds[objectName] = ConfigPcaLed.fromJson(objectName, jsonChild); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.fan)) { + leds[(cIdentifier!, objectName!)] = ConfigPcaLed.fromJson(objectName, jsonChild); + } else if (cIdentifier == ConfigFileObjectIdentifiers.fan) { configPrintCoolingFan = ConfigPrintCoolingFan.fromJson(jsonChild); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.heater_fan)) { - fans[objectName] = ConfigHeaterFan.fromJson(objectName, jsonChild); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.controller_fan)) { - fans[objectName] = ConfigControllerFan.fromJson(objectName, jsonChild); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.temperature_fan)) { - fans[objectName] = ConfigTemperatureFan.fromJson(objectName, jsonChild); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.fan_generic)) { - fans[objectName] = ConfigGenericFan.fromJson(objectName, jsonChild); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.heater_generic)) { - genericHeaters[objectName] = ConfigHeaterGeneric.fromJson(objectName, jsonChild); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.bed_screws)) { + } else if (cIdentifier == ConfigFileObjectIdentifiers.heater_fan) { + fans[(cIdentifier!, objectName!)] = ConfigHeaterFan.fromJson(objectName, jsonChild); + } else if (cIdentifier == ConfigFileObjectIdentifiers.controller_fan) { + fans[(cIdentifier!, objectName!)] = ConfigControllerFan.fromJson(objectName, jsonChild); + } else if (cIdentifier == ConfigFileObjectIdentifiers.temperature_fan) { + fans[(cIdentifier!, objectName!)] = ConfigTemperatureFan.fromJson(objectName, jsonChild); + } else if (cIdentifier == ConfigFileObjectIdentifiers.fan_generic) { + fans[(cIdentifier!, objectName!)] = ConfigGenericFan.fromJson(objectName, jsonChild); + } else if (cIdentifier == ConfigFileObjectIdentifiers.heater_generic) { + genericHeaters[objectName!] = ConfigHeaterGeneric.fromJson(objectName, jsonChild); + } else if (cIdentifier == ConfigFileObjectIdentifiers.bed_screws) { configBedScrews = ConfigBedScrews.fromJson(jsonChild); - } else if (objectIdentifier.isKlipperObject(ConfigFileObjectIdentifiers.screws_tilt_adjust)) { + } else if (cIdentifier == ConfigFileObjectIdentifiers.screws_tilt_adjust) { configScrewsTiltAdjust = ConfigScrewsTiltAdjust.fromJson(jsonChild); } } diff --git a/common/lib/data/dto/machine/printer_builder.dart b/common/lib/data/dto/machine/printer_builder.dart index 21b21f750..eca5f2d7b 100644 --- a/common/lib/data/dto/machine/printer_builder.dart +++ b/common/lib/data/dto/machine/printer_builder.dart @@ -35,7 +35,7 @@ import 'temperature_sensor.dart'; import 'toolhead.dart'; import 'virtual_sd_card.dart'; -final Map _subToPrinterObjects = { +final Map _partialUpdateMethodMappings = { ConfigFileObjectIdentifiers.bed_mesh: PrinterBuilder._updateBedMesh, ConfigFileObjectIdentifiers.bed_screws: PrinterBuilder._updateBedScrew, ConfigFileObjectIdentifiers.configfile: PrinterBuilder._updateConfigFile, @@ -208,7 +208,7 @@ class PrinterBuilder { // The config identifier is not yet supported if (cIdentifier == null) return this; - final updateMethodToCall = _subToPrinterObjects[cIdentifier]; + final updateMethodToCall = _partialUpdateMethodMappings[cIdentifier]; if (updateMethodToCall == null) return this; // // if (updateMethodToCall case _MultiObjectUpdate()) { diff --git a/common/lib/util/extensions/string_extension.dart b/common/lib/util/extensions/string_extension.dart index 2bf3d7565..95456b3b4 100644 --- a/common/lib/util/extensions/string_extension.dart +++ b/common/lib/util/extensions/string_extension.dart @@ -15,14 +15,6 @@ extension MobilerakerString on String { /// E.g. 'temperature_sensor sensor_name' /// Note that it returns (ObjectIdentifier, ObjectName), /// The ObjectIdentifier is always lowercase and - (String, String?) toKlipperObjectIdentifier() { - final trimmed = trim(); - final parts = trimmed.split(RegExp(r'\s+')); - if (parts.length == 1) return (parts[0].toLowerCase(), null); - - return (parts[0].toLowerCase(), trimmed.substring(parts[0].length).trim()); - } - (ConfigFileObjectIdentifiers?, String?) toKlipperObjectIdentifierNEW() { final trimmed = trim(); final parts = trimmed.split(RegExp(r'\s+')); @@ -35,14 +27,6 @@ extension MobilerakerString on String { return (cIdentifier, trimmed.substring(parts[0].length).trim()); } - bool isKlipperObject(ConfigFileObjectIdentifiers objectIdentifier) { - if (objectIdentifier.regex != null) { - return RegExp(objectIdentifier.regex!).hasMatch(this); - } - - return this == objectIdentifier.name; - } - String obfuscate([int nonObfuscated = 4]) { if (isEmpty) return this; if (kDebugMode) return 'Obfuscated($this)'; diff --git a/common/test/unit/marshalling/configfile_marshalling_test.dart b/common/test/unit/marshalling/configfile_marshalling_test.dart index e28374fba..f81925383 100644 --- a/common/test/unit/marshalling/configfile_marshalling_test.dart +++ b/common/test/unit/marshalling/configfile_marshalling_test.dart @@ -7,6 +7,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:common/data/dto/config/config_file.dart'; +import 'package:common/data/dto/config/config_file_object_identifiers_enum.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -152,10 +153,10 @@ void main() { // Verify leds expect(config.leds, hasLength(4)); - expect(config.leds['sb_leds'], isNotNull); - expect(config.leds['case_dotstars'], isNotNull); - expect(config.leds['fysetc_mini12864'], isNotNull); - expect(config.leds['caselight'], isNotNull); + expect(config.leds[(ConfigFileObjectIdentifiers.neopixel, 'sb_leds')], isNotNull); + expect(config.leds[(ConfigFileObjectIdentifiers.dotstar, 'case_dotstars')], isNotNull); + expect(config.leds[(ConfigFileObjectIdentifiers.neopixel, 'fysetc_mini12864')], isNotNull); + expect(config.leds[(ConfigFileObjectIdentifiers.led, 'caselight')], isNotNull); // Verify Print cooling fan expect(config.configPrintCoolingFan, isNotNull); @@ -173,10 +174,10 @@ void main() { // Verify fans expect(config.fans, hasLength(4)); - expect(config.fans['bedfans'], isNotNull); - expect(config.fans['hotend_fan'], isNotNull); - expect(config.fans['skirt fan'], isNotNull); - expect(config.fans['exhaust_fan'], isNotNull); + expect(config.fans[(ConfigFileObjectIdentifiers.fan_generic, 'bedfans')], isNotNull); + expect(config.fans[(ConfigFileObjectIdentifiers.heater_fan, 'hotend_fan')], isNotNull); + expect(config.fans[(ConfigFileObjectIdentifiers.controller_fan, 'skirt fan')], isNotNull); + expect(config.fans[(ConfigFileObjectIdentifiers.heater_fan, 'exhaust_fan')], isNotNull); // Verify Heaters expect(config.genericHeaters, hasLength(0)); diff --git a/common/test/unit/marshalling/printer/extruder_marshalling_test.dart b/common/test/unit/marshalling/printer/extruder_marshalling_test.dart index b4921e8c6..3ecaa81a1 100644 --- a/common/test/unit/marshalling/printer/extruder_marshalling_test.dart +++ b/common/test/unit/marshalling/printer/extruder_marshalling_test.dart @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023. Patrick Schmidt. + * Copyright (c) 2023-2024. Patrick Schmidt. * All rights reserved. */ @@ -74,9 +74,9 @@ void main() { }); test('config key matching', () { - expect('extruder'.isKlipperObject(ConfigFileObjectIdentifiers.extruder), isTrue); - expect('extruder1'.isKlipperObject(ConfigFileObjectIdentifiers.extruder), isTrue); - expect('extruder2'.isKlipperObject(ConfigFileObjectIdentifiers.extruder), isTrue); + expect('extruder'.toKlipperObjectIdentifierNEW(), (ConfigFileObjectIdentifiers.extruder, null)); + expect('extruder1'.toKlipperObjectIdentifierNEW(), (ConfigFileObjectIdentifiers.extruder, null)); + expect('extruder2'.toKlipperObjectIdentifierNEW(), (ConfigFileObjectIdentifiers.extruder, null)); }); } diff --git a/common/test/unit/util/string_extension_test.dart b/common/test/unit/util/string_extension_test.dart index b28753dd0..d543bb73b 100644 --- a/common/test/unit/util/string_extension_test.dart +++ b/common/test/unit/util/string_extension_test.dart @@ -9,58 +9,8 @@ import 'package:flutter_test/flutter_test.dart'; void main() { group('MobilerakerString', () { - // Tests for toKlipperObjectIdentifier - test('isKlipperObject returns true when object name matches', () { - final result = 'temperature_sensor'.isKlipperObject(ConfigFileObjectIdentifiers.temperature_sensor); - expect(result, true); - }); - - test('isKlipperObject returns false when object name does not match', () { - final result = 'temperature_sensor'.isKlipperObject(ConfigFileObjectIdentifiers.extruder); - expect(result, false); - }); - - test('isKlipperObject returns true when object name matches regex', () { - var result = 'extruder1'.isKlipperObject(ConfigFileObjectIdentifiers.extruder); - expect(result, true); - result = 'extruder'.isKlipperObject(ConfigFileObjectIdentifiers.extruder); - expect(result, true); - }); - - test('isKlipperObject returns false when object name does not match regex', () { - final result = 'fam'.isKlipperObject(ConfigFileObjectIdentifiers.extruder); - expect(result, false); - }); - - test('toKlipperObjectIdentifier returns lowercase identifier and null when single word', () { - final result = 'Temperature'.toKlipperObjectIdentifier(); - expect(result, ('temperature', null)); - }); - test('toKlipperObjectIdentifier returns lowercase identifier and trimmed object name when multiple words', () { - final result = 'Temperature sensor_name'.toKlipperObjectIdentifier(); - expect(result, ('temperature', 'sensor_name')); - }); - test('toKlipperObjectIdentifier handles leading and trailing whitespaces', () { - final result = ' Temperature sensor_name '.toKlipperObjectIdentifier(); - expect(result, ('temperature', 'sensor_name')); - }); - - test('toKlipperObjectIdentifier handles multiple whitespaces between words', () { - final result = 'Temperature sensor_name'.toKlipperObjectIdentifier(); - expect(result, ('temperature', 'sensor_name')); - }); - - test('toKlipperObjectIdentifier handles multiple sections with whitespaces', () { - final result = 'Temperature sensor_name extra_part'.toKlipperObjectIdentifier(); - expect(result, ('temperature', 'sensor_name extra_part')); - }); - - test('toKlipperObjectIdentifier handles multiple sections with multiple whitespaces', () { - final result = 'Temperature sensor_name extra_part'.toKlipperObjectIdentifier(); - expect(result, ('temperature', 'sensor_name extra_part')); - }); // Tests for toKlipperObjectIdentifierNEW test('toKlipperObjectIdentifierNEW returns ConfigFileObjectIdentifiers and null when single word', () { @@ -99,27 +49,11 @@ void main() { expect(result, (null, null)); }); - // Tests for isKlipperObject - test('isKlipperObject returns true when object name matches', () { - final result = 'temperature_sensor'.isKlipperObject(ConfigFileObjectIdentifiers.temperature_sensor); - expect(result, true); - }); - - test('isKlipperObject returns false when object name does not match', () { - final result = 'temperature_sensor'.isKlipperObject(ConfigFileObjectIdentifiers.extruder); - expect(result, false); - }); - - test('isKlipperObject returns true when object name matches regex', () { - var result = 'extruder1'.isKlipperObject(ConfigFileObjectIdentifiers.extruder); - expect(result, true); - result = 'extruder'.isKlipperObject(ConfigFileObjectIdentifiers.extruder); - expect(result, true); - }); - - test('isKlipperObject returns false when object name does not match regex', () { - final result = 'fam'.isKlipperObject(ConfigFileObjectIdentifiers.extruder); - expect(result, false); + test('toKlipperObjectIdentifierNEW handles identifiers that use regex', () { + var result = 'extruder1'.toKlipperObjectIdentifierNEW(); + expect(result, (ConfigFileObjectIdentifiers.extruder, null)); + result = 'extruder'.toKlipperObjectIdentifierNEW(); + expect(result, (ConfigFileObjectIdentifiers.extruder, null)); }); test('levenshteinDistance returns correct distance', () { diff --git a/lib/ui/screens/dashboard/components/pins_card.dart b/lib/ui/screens/dashboard/components/pins_card.dart index 5201e04b6..0860c061a 100644 --- a/lib/ui/screens/dashboard/components/pins_card.dart +++ b/lib/ui/screens/dashboard/components/pins_card.dart @@ -364,8 +364,8 @@ class _Led extends ConsumerWidget { @override Widget build(_, WidgetRef ref) { - var ledConfig = ref - .watch(_pinsCardControllerProvider(machineUUID).selectRequireValue((data) => data.ledConfig[led.configName])); + var ledConfig = ref.watch(_pinsCardControllerProvider(machineUUID) + .selectRequireValue((data) => data.ledConfig[(led.kind, led.configName)])); var klippyCanReceiveCommands = ref.watch(_pinsCardControllerProvider(machineUUID).selectRequireValue((data) => data.klippyCanReceiveCommands)); @@ -601,7 +601,7 @@ class _PinsCardController extends _$PinsCardController { Future onEditLed(Led led) async { if (!state.hasValue) return; - ConfigLed? configLed = state.requireValue.ledConfig[led.configName]; + ConfigLed? configLed = state.requireValue.ledConfig[(led.kind, led.configName)]; if (configLed == null) return; String name = beautifyName(led.name); @@ -667,7 +667,7 @@ class _PinsCardPreviewController extends _PinsCardController { DumbLed(name: 'Preview Led', kind: ConfigFileObjectIdentifiers.led), ], ledConfig: { - 'preview led': ConfigDumbLed( + (ConfigFileObjectIdentifiers.led, 'preview led'): ConfigDumbLed( name: 'preview led', ), }, @@ -711,7 +711,7 @@ class _Model with _$Model { const factory _Model({ required bool klippyCanReceiveCommands, required List elements, - required Map ledConfig, + required Map<(ConfigFileObjectIdentifiers, String), ConfigLed> ledConfig, required Map pinConfig, }) = __Model; diff --git a/lib/ui/screens/dashboard/components/temperature_card/heater_sensor_card.dart b/lib/ui/screens/dashboard/components/temperature_card/heater_sensor_card.dart index fe8149132..a05b0c22f 100644 --- a/lib/ui/screens/dashboard/components/temperature_card/heater_sensor_card.dart +++ b/lib/ui/screens/dashboard/components/temperature_card/heater_sensor_card.dart @@ -23,7 +23,6 @@ import 'package:common/service/setting_service.dart'; import 'package:common/service/ui/dialog_service_interface.dart'; import 'package:common/ui/components/async_guard.dart'; import 'package:common/util/extensions/async_ext.dart'; -import 'package:common/util/extensions/double_extension.dart'; import 'package:common/util/extensions/number_format_extension.dart'; import 'package:common/util/extensions/ref_extension.dart'; import 'package:common/util/logger.dart'; @@ -592,7 +591,8 @@ class _Controller extends _$Controller { editTemperatureFan(TemperatureFan temperatureFan) { var configFan = ref - .read(printerProvider(machineUUID).selectAs((value) => value.configFile.fans[temperatureFan.configName])) + .read(printerProvider(machineUUID) + .selectAs((value) => value.configFile.fans[(temperatureFan.kind, temperatureFan.configName)])) .requireValue; ref From 4386211363c6f2ef2536d719a2db114a63ac293b Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Tue, 12 Nov 2024 22:45:49 +0100 Subject: [PATCH 09/64] chore: Printer & PrinterBuilder both use records for keys now. To match the config file, the printer and printer_builder both also use (Identifier, Name) records for the Maps fan, leds, filamentSensors to allow multiple objects with the same name! --- common/lib/data/dto/machine/printer.dart | 7 +++--- .../lib/data/dto/machine/printer_builder.dart | 20 ++++++----------- .../settings/reordable_element.dart | 2 -- .../printer/printer_builder_test.dart | 3 ++- .../components/filament_sensor_watcher.dart | 5 +++-- .../dashboard/components/fans_card.dart | 5 +---- .../dashboard/components/pins_card.dart | 20 +++++++++++------ .../edit/components/fans_ordering_list.dart | 4 ++-- .../edit/components/misc_ordering_list.dart | 22 ++++--------------- .../edit/components/sensor_ordering_list.dart | 4 ++-- 10 files changed, 38 insertions(+), 54 deletions(-) diff --git a/common/lib/data/dto/machine/printer.dart b/common/lib/data/dto/machine/printer.dart index 3b4a93867..5daf79cda 100644 --- a/common/lib/data/dto/machine/printer.dart +++ b/common/lib/data/dto/machine/printer.dart @@ -3,6 +3,7 @@ * All rights reserved. */ +import 'package:common/data/dto/config/config_file_object_identifiers_enum.dart'; import 'package:common/data/dto/machine/bed_mesh/bed_mesh.dart'; import 'package:common/data/dto/machine/filament_sensors/filament_sensor.dart'; import 'package:common/data/dto/machine/gcode_macro.dart'; @@ -56,14 +57,14 @@ class Printer with _$Printer { BedMesh? bedMesh, GCodeFile? currentFile, ZThermalAdjust? zThermalAdjust, - @Default({}) Map fans, + @Default({}) Map<(ConfigFileObjectIdentifiers, String), NamedFan> fans, @Default({}) Map temperatureSensors, @Default({}) Map outputPins, @Default([]) List queryableObjects, @Default({}) Map gcodeMacros, - @Default({}) Map leds, + @Default({}) Map<(ConfigFileObjectIdentifiers, String), Led> leds, @Default({}) Map genericHeaters, - @Default({}) Map filamentSensors, + @Default({}) Map<(ConfigFileObjectIdentifiers, String), FilamentSensor> filamentSensors, }) = _Printer; Extruder get extruder => extruders[0]; // Fast way for first extruder -> always present! diff --git a/common/lib/data/dto/machine/printer_builder.dart b/common/lib/data/dto/machine/printer_builder.dart index eca5f2d7b..7f7e8a09c 100644 --- a/common/lib/data/dto/machine/printer_builder.dart +++ b/common/lib/data/dto/machine/printer_builder.dart @@ -141,14 +141,14 @@ class PrinterBuilder { FirmwareRetraction? firmwareRetraction; BedMesh? bedMesh; ZThermalAdjust? zThermalAdjust; - Map fans = {}; + Map<(ConfigFileObjectIdentifiers, String), NamedFan> fans = {}; Map temperatureSensors = {}; Map outputPins = {}; List queryableObjects = []; Map gcodeMacros = {}; - Map leds = {}; + Map<(ConfigFileObjectIdentifiers, String), Led> leds = {}; Map genericHeaters = {}; - Map filamentSensors = {}; + Map<(ConfigFileObjectIdentifiers, String), FilamentSensor> filamentSensors = {}; Printer build() { if (toolhead == null) { @@ -211,10 +211,6 @@ class PrinterBuilder { final updateMethodToCall = _partialUpdateMethodMappings[cIdentifier]; if (updateMethodToCall == null) return this; // - // if (updateMethodToCall case _MultiObjectUpdate()) { - // return this; - // } - return switch (updateMethodToCall) { _SingleObjectUpdate() => updateMethodToCall(json[key], this), // Extruder is a special case.... @@ -253,7 +249,7 @@ class PrinterBuilder { static PrinterBuilder _updateNamedFan( ConfigFileObjectIdentifiers identifier, String name, Map fanJson, PrinterBuilder builder) { // We need to combine identifier and name again because fans can have the same name as long as they are not the same type causing issues here! - final key = '${identifier.name}::$name'; + final key = (identifier, name); final curFan = builder.fans[key] ?? NamedFan.fallback(identifier, name); return builder..fans = {...builder.fans, key: NamedFan.partialUpdate(curFan, fanJson)}; @@ -265,7 +261,7 @@ class PrinterBuilder { static PrinterBuilder _updateLed( ConfigFileObjectIdentifiers identifier, String name, Map json, PrinterBuilder builder) { - final key = '${identifier.name}::$name'; + final key = (identifier, name); final curLed = builder.leds[key] ?? Led.fallback(identifier, name); return builder..leds = {...builder.leds, key: Led.partialUpdate(curLed, json)}; @@ -300,7 +296,7 @@ class PrinterBuilder { static PrinterBuilder _updateFilamentSensor( ConfigFileObjectIdentifiers identifier, String name, Map json, PrinterBuilder builder) { - final key = '${identifier.name}::$name'; + final key = (identifier, name); final filamentSensor = builder.filamentSensors[key] ?? FilamentSensor.fallback(identifier, name); return builder @@ -377,6 +373,4 @@ class PrinterBuilder { //////////////////////////////// // END CODE to update fields // //////////////////////////////// -} - -String _typeOrNull(dynamic obj) => obj == null ? 'null' : obj.runtimeType.toString(); +} \ No newline at end of file diff --git a/common/lib/data/model/moonraker_db/settings/reordable_element.dart b/common/lib/data/model/moonraker_db/settings/reordable_element.dart index 26410580e..a2267f3fa 100644 --- a/common/lib/data/model/moonraker_db/settings/reordable_element.dart +++ b/common/lib/data/model/moonraker_db/settings/reordable_element.dart @@ -36,7 +36,5 @@ class ReordableElement with _$ReordableElement { factory ReordableElement.fromJson(Map json) => _$ReordableElementFromJson(json); - String get kindName => '${kind.name}::$name'; - String get beautifiedName => beautifyName(name); } diff --git a/common/test/unit/marshalling/printer/printer_builder_test.dart b/common/test/unit/marshalling/printer/printer_builder_test.dart index 4d8f00929..0d2acceab 100644 --- a/common/test/unit/marshalling/printer/printer_builder_test.dart +++ b/common/test/unit/marshalling/printer/printer_builder_test.dart @@ -3,6 +3,7 @@ * All rights reserved. */ +import 'package:common/data/dto/config/config_file_object_identifiers_enum.dart'; import 'package:common/data/dto/machine/fans/generic_fan.dart'; import 'package:common/data/dto/machine/printer_builder.dart'; import 'package:common/exceptions/mobileraker_exception.dart'; @@ -188,7 +189,7 @@ void main() { test('Update generic fan', () { final builder = PrinterBuilder.preview(); - final key = 'fan_generic::hotend_fan'; + final key = (ConfigFileObjectIdentifiers.fan_generic, 'hotend_fan'); builder.fans[key] = GenericFan(name: 'hotend_fan'); final json = { diff --git a/lib/ui/components/filament_sensor_watcher.dart b/lib/ui/components/filament_sensor_watcher.dart index da6454bd9..8297aaa2a 100644 --- a/lib/ui/components/filament_sensor_watcher.dart +++ b/lib/ui/components/filament_sensor_watcher.dart @@ -3,6 +3,7 @@ * All rights reserved. */ +import 'package:common/data/dto/config/config_file_object_identifiers_enum.dart'; import 'package:common/data/dto/machine/filament_sensors/filament_sensor.dart'; import 'package:common/service/moonraker/printer_service.dart'; import 'package:common/service/setting_service.dart'; @@ -33,7 +34,7 @@ class FilamentSensorWatcher extends StatefulHookConsumerWidget { class _FilamentSensorWatcherState extends ConsumerState { DialogService get _dialogService => ref.read(dialogServiceProvider); - ProviderSubscription>>? _subscription; + ProviderSubscription>>? _subscription; bool _enabled = true; @@ -113,7 +114,7 @@ class _FilamentSensorWatcherState extends ConsumerState { // Provider to keep track of triggered filament sensors during the lifetime of the app rather than just the widget @riverpod -Map _triggered(_TriggeredRef ref, String machineUUID) { +Map<(ConfigFileObjectIdentifiers, String), bool> _triggered(_TriggeredRef ref, String machineUUID) { ref.keepAlive(); return {}; } diff --git a/lib/ui/screens/dashboard/components/fans_card.dart b/lib/ui/screens/dashboard/components/fans_card.dart index addecfaa8..0e601bd10 100644 --- a/lib/ui/screens/dashboard/components/fans_card.dart +++ b/lib/ui/screens/dashboard/components/fans_card.dart @@ -317,10 +317,7 @@ class _FansCardController extends _$FansCardController { var printFan = value.printFan; var fans = value.fans; - return [ - if (printFan != null) printFan, - ...fans.values, - ]; + return [if (printFan != null) printFan, ...fans.values]; })) // Use map here since this prevents to many operations if the original list not changes! .map((fans) { diff --git a/lib/ui/screens/dashboard/components/pins_card.dart b/lib/ui/screens/dashboard/components/pins_card.dart index 0860c061a..2b7ba9a9e 100644 --- a/lib/ui/screens/dashboard/components/pins_card.dart +++ b/lib/ui/screens/dashboard/components/pins_card.dart @@ -515,11 +515,7 @@ class _PinsCardController extends _$PinsCardController { var filamentSensors = value.filamentSensors; var pins = value.outputPins; - return [ - ...leds.values, - ...pins.values, - ...filamentSensors.values, - ]; + return [...leds.values, ...pins.values, ...filamentSensors.values]; })) // Use map here since this prevents to many operations if the original list not changes! .map((elements) { @@ -544,8 +540,18 @@ class _PinsCardController extends _$PinsCardController { // Sort output by ordering, if ordering is not found it will be placed at the end output.sort((a, b) { - var aIndex = ordering.indexWhere((element) => element.name == a.name); - var bIndex = ordering.indexWhere((element) => element.name == b.name); + determineKind(obj) => switch (obj) { + Led() => a.kind, + FilamentSensor() => a.kind, + OutputPin() => ConfigFileObjectIdentifiers.output_pin, + _ => null, + }; + + ConfigFileObjectIdentifiers? aKind = determineKind(a); + ConfigFileObjectIdentifiers? bKind = determineKind(b); + + var aIndex = ordering.indexWhere((element) => element.name == a.name && element.kind == aKind); + var bIndex = ordering.indexWhere((element) => element.name == b.name && element.kind == bKind); if (aIndex == -1) aIndex = output.length; if (bIndex == -1) bIndex = output.length; diff --git a/lib/ui/screens/printers/edit/components/fans_ordering_list.dart b/lib/ui/screens/printers/edit/components/fans_ordering_list.dart index 224e3f4f1..3590e3e62 100644 --- a/lib/ui/screens/printers/edit/components/fans_ordering_list.dart +++ b/lib/ui/screens/printers/edit/components/fans_ordering_list.dart @@ -152,14 +152,14 @@ class FansOrderingListController extends _$FansOrderingListController { // Only include elements that are available in the printer for (var setting in settings) { - if (availableElements.any((e) => e.kindName == setting.kindName)) { + if (availableElements.any((e) => e.kind == setting.kind && e.name == setting.name)) { normalizedSettings.add(setting); } } // Add missing elements from the printer for (var element in availableElements) { - if (!normalizedSettings.any((e) => e.kindName == element.kindName)) { + if (!normalizedSettings.any((e) => e.kind == element.kind && e.name == element.name)) { normalizedSettings.add(element); } } diff --git a/lib/ui/screens/printers/edit/components/misc_ordering_list.dart b/lib/ui/screens/printers/edit/components/misc_ordering_list.dart index 25858da47..42e9aaab8 100644 --- a/lib/ui/screens/printers/edit/components/misc_ordering_list.dart +++ b/lib/ui/screens/printers/edit/components/misc_ordering_list.dart @@ -7,8 +7,6 @@ import 'dart:ui'; import 'package:collection/collection.dart'; import 'package:common/data/dto/config/config_file_object_identifiers_enum.dart'; -import 'package:common/data/dto/machine/filament_sensors/filament_motion_sensor.dart'; -import 'package:common/data/dto/machine/filament_sensors/filament_switch_sensor.dart'; import 'package:common/data/dto/machine/printer.dart'; import 'package:common/data/model/moonraker_db/settings/reordable_element.dart'; import 'package:common/service/machine_service.dart'; @@ -138,10 +136,7 @@ class MiscOrderingListController extends _$MiscOrderingListController { var availableElements = []; for (var led in printerData.leds.values) { - availableElements.add(ReordableElement( - kind: ConfigFileObjectIdentifiers.led, - name: led.name, - )); + availableElements.add(ReordableElement(kind: led.kind, name: led.name)); } for (var pin in printerData.outputPins.values) { @@ -152,16 +147,7 @@ class MiscOrderingListController extends _$MiscOrderingListController { } for (var sensor in printerData.filamentSensors.values) { - var kind = switch (sensor) { - FilamentMotionSensor() => ConfigFileObjectIdentifiers.filament_motion_sensor, - FilamentSwitchSensor() => ConfigFileObjectIdentifiers.filament_switch_sensor, - _ => throw UnimplementedError('Unknown sensor type: $sensor'), - }; - - availableElements.add(ReordableElement( - kind: kind, - name: sensor.name, - )); + availableElements.add(ReordableElement(kind: sensor.kind, name: sensor.name)); } return availableElements; } @@ -172,14 +158,14 @@ class MiscOrderingListController extends _$MiscOrderingListController { // Only include elements that are available in the printer for (var setting in settings) { - if (availableElements.any((e) => e.kindName == setting.kindName)) { + if (availableElements.any((e) => e.kind == setting.kind && e.name == setting.name)) { normalizedSettings.add(setting); } } // Add missing elements from the printer for (var element in availableElements) { - if (!normalizedSettings.any((e) => e.kindName == element.kindName)) { + if (!normalizedSettings.any((e) => e.kind == element.kind && e.name == element.name)) { normalizedSettings.add(element); } } diff --git a/lib/ui/screens/printers/edit/components/sensor_ordering_list.dart b/lib/ui/screens/printers/edit/components/sensor_ordering_list.dart index 6ce3a28c1..a9a4994ff 100644 --- a/lib/ui/screens/printers/edit/components/sensor_ordering_list.dart +++ b/lib/ui/screens/printers/edit/components/sensor_ordering_list.dart @@ -166,14 +166,14 @@ class SensorOrderingListController extends _$SensorOrderingListController { // Only include elements that are available in the printer for (var setting in settings) { - if (availableElements.any((e) => e.kindName == setting.kindName)) { + if (availableElements.any((e) => e.kind == setting.kind && e.name == setting.name)) { normalizedSettings.add(setting); } } // Add missing elements from the printer for (var element in availableElements) { - if (!normalizedSettings.any((e) => e.kindName == element.kindName)) { + if (!normalizedSettings.any((e) => e.kind == element.kind && e.name == element.name)) { normalizedSettings.add(element); } } From 6a724ad7484441ed1aa3964e449a7225ebba9da7 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Tue, 12 Nov 2024 23:06:09 +0100 Subject: [PATCH 10/64] fix: Filament Sensor Dialog not opening. Due to a setup issue the filament sensor dialog was not triggered correctly --- docs/changelog.md | 5 +++++ lib/ui/components/filament_sensor_watcher.dart | 11 ++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 85ef5b637..78bff52e6 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,6 +6,11 @@ - **GCode-Preview**: Fixed an issue within the GCode-Parser that caused the preview to show an error if the `SET_RETRACTION` was used in the GCode file. +- **Object Naming**: Klipper objects (Config entries) of the same class with the same name no longer cause the app to + show a permanent error message. This could happen if the user configured different kind of fans, e.g. a `fan_generic` + and `temperature_fan`, with the same name. The app is now aware of the object kind and will allow users to have + objects of the same class with + the same name but different kind, applies to fans, leds and filament sensors. ## [2.8.2] - 2024-10-31 diff --git a/lib/ui/components/filament_sensor_watcher.dart b/lib/ui/components/filament_sensor_watcher.dart index 8297aaa2a..853f3e859 100644 --- a/lib/ui/components/filament_sensor_watcher.dart +++ b/lib/ui/components/filament_sensor_watcher.dart @@ -36,7 +36,7 @@ class _FilamentSensorWatcherState extends ConsumerState { ProviderSubscription>>? _subscription; - bool _enabled = true; + bool? _enabled; @override void initState() { @@ -47,7 +47,7 @@ class _FilamentSensorWatcherState extends ConsumerState { logger.i('FilamentSensorWatcher: filamentSensorDialog setting changed from $previous to $next'); if (next != _enabled) { _enabled = next; - if (_enabled) { + if (_enabled == true) { _setup(); } else { _subscription?.close(); @@ -60,7 +60,7 @@ class _FilamentSensorWatcherState extends ConsumerState { @override void didUpdateWidget(FilamentSensorWatcher oldWidget) { - if (_enabled && oldWidget.machineUUID != widget.machineUUID) _setup(); + if (_enabled == true && oldWidget.machineUUID != widget.machineUUID) _setup(); super.didUpdateWidget(oldWidget); } @@ -71,13 +71,14 @@ class _FilamentSensorWatcherState extends ConsumerState { } void _setup() { + logger.i('FilamentSensorWatcher: Setting up filamentSensorWatcher for ${widget.machineUUID}'); _subscription?.close(); _subscription = ref.listenManual( printerProvider(widget.machineUUID).selectAs((d) => d.filamentSensors), (previous, next) { if (!next.hasValue) return; if (_dialogService.isDialogOpen) return; - if (!_enabled) return; + if (_enabled != true) return; var filamentSensors = next.value!; @@ -88,7 +89,7 @@ class _FilamentSensorWatcherState extends ConsumerState { if (_dialogService.isDialogOpen) return; if (sensor.enabled && !sensor.filamentDetected && model[entry.key] != true) { - logger.i('Detected filamentSensor triggered ${sensor.name}... opening Dialog'); + logger.i('FilamentSensorWatcher: Detected filamentSensor triggered ${sensor.name}... opening Dialog'); model[entry.key] = true; _dialogService.show(DialogRequest( type: DialogType.info, From 694079e4678257c7a6b9df4c6e996fbbec9883e4 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Thu, 14 Nov 2024 23:16:14 +0100 Subject: [PATCH 11/64] feat: Adds fine grain control over Macro visibility based on print state --- assets/translations/de.yaml | 3 +- assets/translations/en.yaml | 3 +- .../moonraker_db/settings/gcode_macro.dart | 8 ++- .../moonraker_db/settings/macro_group.dart | 9 +-- docs/changelog.md | 7 +++ .../macro_settings/macro_settings_dialog.dart | 60 ++++++++++++++++--- .../components/macro_group_card.dart | 46 ++++++-------- .../edit/components/macro_group_list.dart | 4 +- 8 files changed, 93 insertions(+), 47 deletions(-) diff --git a/assets/translations/de.yaml b/assets/translations/de.yaml index 1707f78e0..ac0c144ad 100644 --- a/assets/translations/de.yaml +++ b/assets/translations/de.yaml @@ -1049,7 +1049,8 @@ dialogs: value: Header-Wert value_hint: Der Wert des HTTP-Headers macro_settings: - show_while_printing: Während des Drucks anzeigen + show_for_states: Druck-Zustände + show_for_states_hint: Wählen Sie die Druckzustände, in denen das Makro angezeigt werden soll visible: Sichtbar extruder_feedrate: title: Extruder-Geschwindigkeit [mm/s] diff --git a/assets/translations/en.yaml b/assets/translations/en.yaml index 2779dab62..697b6261b 100644 --- a/assets/translations/en.yaml +++ b/assets/translations/en.yaml @@ -1131,7 +1131,8 @@ dialogs: value: Header-Value value_hint: The value of the header macro_settings: - show_while_printing: Show while printing + show_for_states: Print States + show_for_states_hint: Select the states for which the macro should be displayed visible: Visible extruder_feedrate: title: Extruder Velocity [mm/s] diff --git a/common/lib/data/model/moonraker_db/settings/gcode_macro.dart b/common/lib/data/model/moonraker_db/settings/gcode_macro.dart index 1afd7fd10..5cb4ddc94 100644 --- a/common/lib/data/model/moonraker_db/settings/gcode_macro.dart +++ b/common/lib/data/model/moonraker_db/settings/gcode_macro.dart @@ -6,6 +6,8 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:uuid/uuid.dart'; +import '../../../dto/machine/print_state_enum.dart'; + part 'gcode_macro.freezed.dart'; part 'gcode_macro.g.dart'; @@ -17,20 +19,20 @@ class GCodeMacro with _$GCodeMacro { required String uuid, required String name, @Default(true) bool visible, - @Default(true) bool showWhilePrinting, + @Default({...PrintState.values}) Set showForState, DateTime? forRemoval, }) = _GCodeMacro; factory GCodeMacro({ required String name, bool visible = true, - bool showWhilePrinting = true, + Set showForState = const {...PrintState.values}, }) { return GCodeMacro.__( uuid: const Uuid().v4(), name: name, visible: visible, - showWhilePrinting: showWhilePrinting, + showForState: showForState, ); } diff --git a/common/lib/data/model/moonraker_db/settings/macro_group.dart b/common/lib/data/model/moonraker_db/settings/macro_group.dart index 32eea75fe..c16a3481b 100644 --- a/common/lib/data/model/moonraker_db/settings/macro_group.dart +++ b/common/lib/data/model/moonraker_db/settings/macro_group.dart @@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:uuid/uuid.dart'; +import '../../../dto/machine/print_state_enum.dart'; import 'gcode_macro.dart'; part 'macro_group.freezed.dart'; @@ -49,14 +50,14 @@ class MacroGroup with _$MacroGroup { bool get isDefaultGroup => uuid == 'default'; - bool hasMacros(bool isPrinting) { + bool hasMacros(PrintState printState) { return macros - .any((element) => element.visible && (!isPrinting || element.showWhilePrinting) && element.forRemoval == null); + .any((element) => element.visible && element.showForState.contains(printState) && element.forRemoval == null); } - List filtered(bool isPrinting) { + List filtered(PrintState printState) { return macros - .where((element) => element.visible && (!isPrinting || element.showWhilePrinting) && element.forRemoval == null) + .where((element) => element.visible && element.showForState.contains(printState) && element.forRemoval == null) .toList(); } } diff --git a/docs/changelog.md b/docs/changelog.md index 78bff52e6..3f0e4e5b5 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,13 @@ ## [2.8.3] - 2024-11-xx +### Enhancements + +- **Macro Visibility**: It is now possible to configure the actual printer state for which a macro should be visible. + This + allows users to hide macros that are not relevant for the current printer state. To adjust the visibility of macros, + just tap on the macro within the group editor of the printer edit page. + ### Bug Fixes - **GCode-Preview**: Fixed an issue within the GCode-Parser that caused the preview to show an error if the diff --git a/lib/ui/components/dialog/macro_settings/macro_settings_dialog.dart b/lib/ui/components/dialog/macro_settings/macro_settings_dialog.dart index e1c17c3e1..50517ce72 100644 --- a/lib/ui/components/dialog/macro_settings/macro_settings_dialog.dart +++ b/lib/ui/components/dialog/macro_settings/macro_settings_dialog.dart @@ -3,9 +3,11 @@ * All rights reserved. */ +import 'package:common/data/dto/machine/print_state_enum.dart'; import 'package:common/data/model/moonraker_db/settings/gcode_macro.dart'; import 'package:common/service/ui/dialog_service_interface.dart'; import 'package:common/ui/dialog/mobileraker_dialog.dart'; +import 'package:common/util/extensions/object_extension.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -69,10 +71,15 @@ class _MacroSettingsDialog extends ConsumerWidget { onChanged: controller.onVisibleChanged, title: const Text('dialogs.macro_settings.visible').tr(), ), - _Switch( - value: model.showWhilePrinting, - onChanged: controller.onShowWhilePrintingChanged, - title: const Text('dialogs.macro_settings.show_while_printing').tr(), + _WrapStates( + inputDecoration: InputDecoration( + labelText: 'dialogs.macro_settings.show_for_states'.tr(), + labelStyle: themeData.textTheme.bodyLarge, + helperText: 'dialogs.macro_settings.show_for_states_hint'.tr(), + helperMaxLines: 10, + ), + active: model.showForState, + onChanged: controller.onShowForStateChanged, ), ], ), @@ -112,6 +119,37 @@ class _Switch extends StatelessWidget { } } +class _WrapStates extends StatelessWidget { + const _WrapStates({super.key, required this.inputDecoration, required this.active, required this.onChanged}); + + final InputDecoration inputDecoration; + final Set active; + final void Function(PrintState, bool) onChanged; + + @override + Widget build(BuildContext context) { + var themeData = Theme.of(context); + return InputDecorator( + decoration: inputDecoration, + child: Wrap( + alignment: WrapAlignment.spaceEvenly, + children: [ + for (var state in PrintState.values) + FilterChip( + selected: active.contains(state), + avatar: const Icon(Icons.circle_outlined).unless(active.contains(state)), + iconTheme: IconThemeData(color: themeData.disabledColor), + checkmarkColor: themeData.colorScheme.primary, + elevation: 2, + label: Text(state.displayName), + onSelected: (bool s) => onChanged(state, s), + ), + ], + ), + ); + } +} + @riverpod class _MacroSettingsDialogController extends _$MacroSettingsDialogController { @override @@ -119,15 +157,19 @@ class _MacroSettingsDialogController extends _$MacroSettingsDialogController { return request.data as GCodeMacro; } - cancel() => completer(DialogResponse(confirmed: false)); + void cancel() => completer(DialogResponse(confirmed: false)); - save() => completer(DialogResponse(confirmed: true, data: state)); + void save() => completer(DialogResponse(confirmed: true, data: state)); - onVisibleChanged(bool value) { + void onVisibleChanged(bool value) { state = state.copyWith(visible: value); } - onShowWhilePrintingChanged(bool value) { - state = state.copyWith(showWhilePrinting: value); + void onShowForStateChanged(PrintState printState, bool selected) { + if (selected) { + state = state.copyWith(showForState: {...state.showForState, printState}); + } else { + state = state.copyWith(showForState: {...state.showForState}..remove(printState)); + } } } diff --git a/lib/ui/screens/dashboard/components/macro_group_card.dart b/lib/ui/screens/dashboard/components/macro_group_card.dart index 48b049e6b..a5a50d8c5 100644 --- a/lib/ui/screens/dashboard/components/macro_group_card.dart +++ b/lib/ui/screens/dashboard/components/macro_group_card.dart @@ -172,9 +172,8 @@ class _SelectedGroup extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - var isPrinting = - ref.watch(_macroGroupCardControllerProvider(machineUUID).selectAs((data) => data.isPrinting)).valueOrNull == - true; + var printState = + ref.watch(_macroGroupCardControllerProvider(machineUUID).selectRequireValue((data) => data.printState)); var groupProvider = _macroGroupCardControllerProvider(machineUUID) .selectAs((value) => value.groups.elementAtOrNull(value.selected)); var group = ref.watch(groupProvider).valueOrNull; @@ -213,13 +212,13 @@ class _SelectedGroup extends HookConsumerWidget { sizeDuration: dur, fadeDuration: dur, // The column is required to make it stretch - child: group.hasMacros(isPrinting) + child: group.hasMacros(printState) ? Column( key: ValueKey('group-${group.uuid}'), mainAxisSize: MainAxisSize.min, children: [ _ChipsWrap( - macros: group.filtered(isPrinting), + macros: group.filtered(printState), machineUUID: machineUUID, showAll: showAll.value, hasMoreMacros: (value) => showAllAvailable.value = value || showAll.value, @@ -338,27 +337,20 @@ class _MacroChip extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - var controller = ref.watch(_macroGroupCardControllerProvider(machineUUID).notifier); - - // Test if the macro is available on the printer - ConfigGcodeMacro? configMacro = ref - .watch(_macroGroupCardControllerProvider(machineUUID).selectAs((data) => data.configMacros[macro.configName])) - .valueOrNull; + final controller = ref.watch(_macroGroupCardControllerProvider(machineUUID).notifier); - var klippyCanReceiveCommands = ref - .watch(_macroGroupCardControllerProvider(machineUUID).selectAs((data) => data.klippyCanReceiveCommands)) - .valueOrNull ?? - false; + final (configMacro, klippyCanReceiveCommands, printState) = + ref.watch(_macroGroupCardControllerProvider(machineUUID).selectRequireValue((data) => ( + data.configMacros[macro.configName], + data.klippyCanReceiveCommands, + data.printState, + ))); - var isPrinting = - ref.watch(_macroGroupCardControllerProvider(machineUUID).selectAs((data) => data.isPrinting)).valueOrNull ?? - false; - - var themeData = Theme.of(context); + final themeData = Theme.of(context); var enabled = klippyCanReceiveCommands; //Note, the visibility is just an additional check. The group already filters out removed macros and the once that should not be shown while printing/are hidden return Visibility( - visible: configMacro != null && macro.visible && (!isPrinting || macro.showWhilePrinting), + visible: configMacro != null && macro.visible && macro.showForState.contains(printState), child: GestureDetector( onLongPress: enabled ? () => controller.onMacroLongPressed(configMacro!) : null, child: ActionChip( @@ -395,8 +387,8 @@ class _MacroGroupCardController extends _$MacroGroupCardController { var klippyCanReceiveCommands = ref.watchAsSubject( klipperProvider(machineUUID).selectAs((value) => value.klippyCanReceiveCommands), ); - var isPrinting = ref.watchAsSubject( - printerProvider(machineUUID).selectAs((data) => data.print.state == PrintState.printing), + var printState = ref.watchAsSubject( + printerProvider(machineUUID).selectAs((data) => data.print.state), ); var groups = ref.watchAsSubject( @@ -409,11 +401,11 @@ class _MacroGroupCardController extends _$MacroGroupCardController { var initialIndex = _settingService.readInt(_settingsKey, 0); - yield* Rx.combineLatest4(klippyCanReceiveCommands, groups, configMacros, isPrinting, (a, b, c, d) { + yield* Rx.combineLatest4(klippyCanReceiveCommands, groups, configMacros, printState, (a, b, c, d) { var idx = state.whenData((value) => value.selected).valueOrNull ?? initialIndex; return _Model( klippyCanReceiveCommands: a, - isPrinting: d, + printState: d, groups: b, selected: min(b.length - 1, max(0, idx)), configMacros: c, @@ -461,7 +453,7 @@ class _MacroGroupCardPreviewController extends _MacroGroupCardController { Stream<_Model> build(String machineUUID) { state = AsyncValue.data(_Model( klippyCanReceiveCommands: true, - isPrinting: false, + printState: PrintState.standby, groups: [ MacroGroup( name: 'Preview Group', @@ -512,7 +504,7 @@ class _Model with _$Model { const factory _Model({ required bool klippyCanReceiveCommands, - required bool isPrinting, + required PrintState printState, required int selected, required List groups, required Map configMacros, // Raw Macros available on the printer diff --git a/lib/ui/screens/printers/edit/components/macro_group_list.dart b/lib/ui/screens/printers/edit/components/macro_group_list.dart index 92b9d5ad0..61f03676a 100644 --- a/lib/ui/screens/printers/edit/components/macro_group_list.dart +++ b/lib/ui/screens/printers/edit/components/macro_group_list.dart @@ -237,8 +237,8 @@ class _MacroGroup extends HookConsumerWidget { Icon? _macroAvatar(GCodeMacro macro) { if (!macro.visible) return const Icon(Icons.visibility_off_outlined); - if (!macro.showWhilePrinting) return const Icon(Icons.disabled_visible_outlined); - return null; + // if (!macro.s) return const Icon(Icons.disabled_visible_outlined); + return const Icon(Icons.visibility_outlined); } } From d89bd4903b509d5b18a2f04ed73c3249565e8799 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Thu, 14 Nov 2024 23:17:07 +0100 Subject: [PATCH 12/64] fix: Adjusted UX for print state notification chips --- lib/ui/screens/setting/setting_page.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/ui/screens/setting/setting_page.dart b/lib/ui/screens/setting/setting_page.dart index 483397e12..aac6f17e6 100644 --- a/lib/ui/screens/setting/setting_page.dart +++ b/lib/ui/screens/setting/setting_page.dart @@ -18,6 +18,7 @@ import 'package:common/ui/theme/theme_pack.dart'; import 'package:common/util/extensions/analytics_extension.dart'; import 'package:common/util/extensions/async_ext.dart'; import 'package:common/util/extensions/build_context_extension.dart'; +import 'package:common/util/extensions/object_extension.dart'; import 'package:common/util/logger.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; @@ -806,6 +807,9 @@ class _StateNotificationSettingField extends ConsumerWidget { children: PrintState.values.map((e) { var selected = value.contains(e); return FilterChip( + avatar: const Icon(Icons.circle_outlined).unless(selected), + iconTheme: IconThemeData(color: themeData.disabledColor), + checkmarkColor: themeData.colorScheme.primary, selected: selected, elevation: 2, label: Text(e.displayName), From d1b501fb5610ece02f008a2a1f10f39ed4089397 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Thu, 14 Nov 2024 23:23:12 +0100 Subject: [PATCH 13/64] fix: Adjusted Icons for Print-State Notification selection --- lib/ui/screens/setting/setting_page.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/ui/screens/setting/setting_page.dart b/lib/ui/screens/setting/setting_page.dart index aac6f17e6..c67c21f46 100644 --- a/lib/ui/screens/setting/setting_page.dart +++ b/lib/ui/screens/setting/setting_page.dart @@ -18,7 +18,6 @@ import 'package:common/ui/theme/theme_pack.dart'; import 'package:common/util/extensions/analytics_extension.dart'; import 'package:common/util/extensions/async_ext.dart'; import 'package:common/util/extensions/build_context_extension.dart'; -import 'package:common/util/extensions/object_extension.dart'; import 'package:common/util/logger.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; @@ -807,9 +806,15 @@ class _StateNotificationSettingField extends ConsumerWidget { children: PrintState.values.map((e) { var selected = value.contains(e); return FilterChip( - avatar: const Icon(Icons.circle_outlined).unless(selected), - iconTheme: IconThemeData(color: themeData.disabledColor), - checkmarkColor: themeData.colorScheme.primary, + avatar: AnimatedCrossFade( + firstChild: Icon(Icons.circle_notifications, color: themeData.colorScheme.primary), + secondChild: Icon(Icons.circle_outlined, color: themeData.disabledColor), + crossFadeState: selected ? CrossFadeState.showFirst : CrossFadeState.showSecond, + duration: kThemeAnimationDuration, + firstCurve: Curves.easeInOutCirc, + secondCurve: Curves.easeInOutCirc, + ), + showCheckmark: false, selected: selected, elevation: 2, label: Text(e.displayName), From fa3aea65f762669aaedcd156b20fafdc916c39ed Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Thu, 14 Nov 2024 23:50:00 +0100 Subject: [PATCH 14/64] chore: Bump version 2.8.2 -> 2.8.3 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index b44a5522d..3e4ff56ce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 2.8.2+510 +version: 2.8.3+510 environment: sdk: '>=3.2.3 <4.0.0' From 472af260cbecf4d7ae0334c8e242ece634476fbd Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 17 Nov 2024 13:16:10 +0100 Subject: [PATCH 15/64] fix: Jrpc Sink close code --- common/lib/network/json_rpc_client.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/network/json_rpc_client.dart b/common/lib/network/json_rpc_client.dart index 142b91d12..840098c25 100644 --- a/common/lib/network/json_rpc_client.dart +++ b/common/lib/network/json_rpc_client.dart @@ -158,7 +158,7 @@ class JsonRpcClient { /// Closes the WebSocket communication _resetChannel() { - _channel?.sink.close(WebSocketStatus.goingAway).ignore(); + _channel?.sink.close(WebSocketStatus.normalClosure).ignore(); } /// Ensures that the ws is still connected. From 017da0647515d08dd8e402dba9b092dc315f15cf Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 17 Nov 2024 14:57:37 +0100 Subject: [PATCH 16/64] perf: Ensured webrtc cam is not destroyed to fast! --- lib/ui/components/mjpeg/mjpeg.dart | 42 +++++++++++++++++------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/ui/components/mjpeg/mjpeg.dart b/lib/ui/components/mjpeg/mjpeg.dart index 3a9e9a3f8..2487d06fb 100644 --- a/lib/ui/components/mjpeg/mjpeg.dart +++ b/lib/ui/components/mjpeg/mjpeg.dart @@ -8,6 +8,7 @@ import 'dart:async'; import 'package:common/service/misc_providers.dart'; import 'package:common/ui/components/async_guard.dart'; import 'package:common/util/extensions/async_ext.dart'; +import 'package:common/util/extensions/ref_extension.dart'; import 'package:common/util/extensions/uri_extension.dart'; import 'package:common/util/logger.dart'; import 'package:dio/dio.dart'; @@ -271,24 +272,29 @@ class _MjpegController extends _$MjpegController { @override Stream<_Model> build(Dio dio, MjpegConfig config) async* { - // await Future.delayed(const Duration(seconds: 15)); - - ref.listen(appLifecycleProvider, (_, appState) { - switch (appState) { - case AppLifecycleState.resumed: - _manager.start(); - break; - - case AppLifecycleState.paused: - _manager.stop(); - break; - default: - // Do Nothing - } - }); - - var manager = ref.watch(mjpegManagerProvider(dio, config)); - manager.start(); + // Get the manager + final manager = ref.watch(mjpegManagerProvider(dio, config)); + + // Listen to the app lifecycle to start/stop the camera + ref.listen( + appLifecycleProvider, + (_, appState) { + switch (appState) { + case AppLifecycleState.resumed: + _manager.start(); + break; + + case AppLifecycleState.paused: + _manager.stop(); + break; + default: + // Do Nothing + } + }, + fireImmediately: true, + ); + + ref.keepAliveFor(); yield* manager.jpegStream.doOnData(_frameReceived).map((event) => _Model(fps: _fps, image: event)); } From 0af18d5d3017272cd897583f07d8c33172adcf8d Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 17 Nov 2024 14:58:58 +0100 Subject: [PATCH 17/64] doc: Updated changelog for webRTC change --- docs/changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 3f0e4e5b5..9696fdf07 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -19,6 +19,9 @@ objects of the same class with the same name but different kind, applies to fans, leds and filament sensors. +- **WebRtc Cam Creation**: Fixed an issue in the code that handles the creation of WebRtc cams in the app's internals. + This caused to many webRTC cam streams to be opened in a short time, which could lead to a crash of the app. + ## [2.8.2] - 2024-10-31 ### Bug Fixes From 179d429ab1bffacae7add0c5ef5608fd4db6bef1 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 17 Nov 2024 15:20:55 +0100 Subject: [PATCH 18/64] build: Updated riverpod dependencies to 2.6.x --- common/pubspec.yaml | 10 +++++----- pubspec.yaml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/common/pubspec.yaml b/common/pubspec.yaml index f5ccf7815..b6fd9fbe3 100644 --- a/common/pubspec.yaml +++ b/common/pubspec.yaml @@ -54,8 +54,8 @@ dependencies: #architecture freezed_annotation: ^2.4.1 flutter_hooks: ^0.20.1 - hooks_riverpod: ^2.3.8 - riverpod_annotation: ^2.1.6 + hooks_riverpod: ^2.6.1 + riverpod_annotation: ^2.6.1 json_annotation: ^4.8.1 #i18n @@ -102,12 +102,12 @@ dev_dependencies: sdk: flutter mockito: ^5.3.2 flutter_lints: ^4.0.0 - riverpod_lint: ^2.3.13 - custom_lint: ^0.6.4 + riverpod_lint: ^2.6.2 + custom_lint: ^0.7.0 build_runner: ^2.4.9 freezed: ^2.5.2 json_serializable: ^6.4.1 - riverpod_generator: ^2.4.3 + riverpod_generator: ^2.6.2 hive_ce_generator: ^1.6.0 flutter: diff --git a/pubspec.yaml b/pubspec.yaml index 3e4ff56ce..f4b07d07a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,8 +55,8 @@ dependencies: #architecture freezed_annotation: ^2.4.1 flutter_hooks: ^0.20.1 - hooks_riverpod: ^2.3.8 - riverpod_annotation: ^2.1.2 + hooks_riverpod: ^2.6.1 + riverpod_annotation: ^2.6.1 json_annotation: ^4.7.0 #routing @@ -176,11 +176,11 @@ dev_dependencies: freezed: ^2.5.2 mockito: ^5.3.2 json_serializable: ^6.4.1 - riverpod_generator: ^2.4.3 + riverpod_generator: ^2.6.2 # riverpod_lint makes it easier to work with Riverpod - riverpod_lint: ^2.3.13 + riverpod_lint: ^2.6.2 # import custom_lint too as riverpod_lint depends on it - custom_lint: ^0.6.4 + custom_lint: ^0.7.0 flutter_native_splash: ^2.2.19 flutter_launcher_icons: ^0.13.1 dart_code_metrics_presets: ^2.5.0 From 06874aec1192d945c556a1ebdcc5891e515b20fe Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 17 Nov 2024 20:42:58 +0100 Subject: [PATCH 19/64] fix: Migrated common providers to new style of Ref --- .../dashboard_layout_hive_repository.dart | 3 +- .../repository/fcm/apns_repository_impl.dart | 3 +- .../device_fcm_settings_repository_impl.dart | 3 +- ...notification_settings_repository_impl.dart | 5 +- .../repository/machine_hive_repository.dart | 3 +- ...machine_settings_moonraker_repository.dart | 4 +- .../notifications_hive_repository.dart | 3 +- .../repository/webcam_info_repository.dart | 3 +- common/lib/network/dio_provider.dart | 9 +- common/lib/network/http_client_factory.dart | 3 +- common/lib/network/jrpc_client_provider.dart | 18 +-- .../network/moonraker_database_client.dart | 4 +- common/lib/service/app_router.dart | 3 +- common/lib/service/date_format_service.dart | 3 +- common/lib/service/firebase/analytics.dart | 3 +- common/lib/service/firebase/auth.dart | 5 +- common/lib/service/firebase/firestore.dart | 3 +- .../lib/service/firebase/remote_config.dart | 13 +- common/lib/service/live_activity_service.dart | 6 +- .../lib/service/live_activity_service_v2.dart | 4 +- common/lib/service/machine_service.dart | 116 +++++++++--------- common/lib/service/misc_providers.dart | 9 +- .../moonraker/announcement_service.dart | 7 +- .../lib/service/moonraker/file_service.dart | 24 ++-- .../moonraker/klipper_system_service.dart | 48 ++++---- .../lib/service/moonraker/klippy_service.dart | 10 +- .../lib/service/moonraker/power_service.dart | 6 +- .../service/moonraker/printer_service.dart | 73 ++++++----- .../lib/service/moonraker/webcam_service.dart | 8 +- common/lib/service/notification_service.dart | 10 +- .../service/obico/obico_tunnel_service.dart | 4 +- .../app_connection_service.dart | 4 +- .../octoeverywhere/gadget_service.dart | 6 +- common/lib/service/payment_service.dart | 58 +++++---- .../lib/service/selected_machine_service.dart | 6 +- common/lib/service/setting_service.dart | 15 +-- .../ui/bottom_sheet_service_interface.dart | 3 +- .../service/ui/dialog_service_interface.dart | 3 +- .../ui/snackbar_service_interface.dart | 3 +- common/lib/service/ui/theme_service.dart | 11 +- .../components/nav/nav_widget_controller.dart | 14 +-- common/lib/ui/components/spool_widget.dart | 4 +- common/lib/util/extensions/ref_extension.dart | 2 +- .../components/control_xyz_card.dart | 3 +- .../temperature_card/heater_sensor_card.dart | 4 +- lib/ui/screens/dev/dev_page.dart | 2 +- .../edit/printers_edit_controller.dart | 4 +- .../screens/spoolman/filament_form_page.dart | 6 +- .../screens/spoolman/spool_detail_page.dart | 2 +- lib/ui/screens/spoolman/vendor_form_page.dart | 4 +- 50 files changed, 307 insertions(+), 263 deletions(-) diff --git a/common/lib/data/repository/dashboard_layout_hive_repository.dart b/common/lib/data/repository/dashboard_layout_hive_repository.dart index 96ea9d9a2..9127332e5 100644 --- a/common/lib/data/repository/dashboard_layout_hive_repository.dart +++ b/common/lib/data/repository/dashboard_layout_hive_repository.dart @@ -6,6 +6,7 @@ import 'package:common/data/model/hive/dashboard_layout.dart'; import 'package:common/exceptions/mobileraker_exception.dart'; import 'package:hive_ce/hive.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../util/logger.dart'; @@ -14,7 +15,7 @@ import 'dashboard_layout_repository.dart'; part 'dashboard_layout_hive_repository.g.dart'; @Riverpod(keepAlive: true) -DashboardLayoutHiveRepository dashboardLayoutHiveRepository(DashboardLayoutHiveRepositoryRef ref) { +DashboardLayoutHiveRepository dashboardLayoutHiveRepository(Ref ref) { return DashboardLayoutHiveRepository(); } diff --git a/common/lib/data/repository/fcm/apns_repository_impl.dart b/common/lib/data/repository/fcm/apns_repository_impl.dart index 6a8fc5d6b..ab484eb6a 100644 --- a/common/lib/data/repository/fcm/apns_repository_impl.dart +++ b/common/lib/data/repository/fcm/apns_repository_impl.dart @@ -3,6 +3,7 @@ * All rights reserved. */ +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../network/moonraker_database_client.dart'; @@ -12,7 +13,7 @@ import 'apns_repository.dart'; part 'apns_repository_impl.g.dart'; @riverpod -APNsRepository apnsRepository(ApnsRepositoryRef ref, String machineUUID) { +APNsRepository apnsRepository(Ref ref, String machineUUID) { return APNsRepositoryImpl(ref.watch(moonrakerDatabaseClientProvider(machineUUID))); } diff --git a/common/lib/data/repository/fcm/device_fcm_settings_repository_impl.dart b/common/lib/data/repository/fcm/device_fcm_settings_repository_impl.dart index 27813dd3e..63b827e64 100644 --- a/common/lib/data/repository/fcm/device_fcm_settings_repository_impl.dart +++ b/common/lib/data/repository/fcm/device_fcm_settings_repository_impl.dart @@ -3,6 +3,7 @@ * All rights reserved. */ +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:uuid/uuid.dart'; @@ -13,7 +14,7 @@ import '../fcm/device_fcm_settings_repository.dart'; part 'device_fcm_settings_repository_impl.g.dart'; @riverpod -DeviceFcmSettingsRepository deviceFcmSettingsRepository(DeviceFcmSettingsRepositoryRef ref, String machineUUID) { +DeviceFcmSettingsRepository deviceFcmSettingsRepository(Ref ref, String machineUUID) { return DeviceFcmSettingsRepositoryImpl(ref.watch(moonrakerDatabaseClientProvider(machineUUID))); } diff --git a/common/lib/data/repository/fcm/notification_settings_repository_impl.dart b/common/lib/data/repository/fcm/notification_settings_repository_impl.dart index 71de96ea7..01147c26d 100644 --- a/common/lib/data/repository/fcm/notification_settings_repository_impl.dart +++ b/common/lib/data/repository/fcm/notification_settings_repository_impl.dart @@ -14,12 +14,11 @@ import '../fcm/notification_settings_repository.dart'; part 'notification_settings_repository_impl.g.dart'; @riverpod -NotificationSettingsRepository notificationSettingsRepository( - NotificationSettingsRepositoryRef ref, String machineUUID) => +NotificationSettingsRepository notificationSettingsRepository(Ref ref, String machineUUID) => NotificationSettingsRepositoryImpl(ref, machineUUID); class NotificationSettingsRepositoryImpl extends NotificationSettingsRepository { - NotificationSettingsRepositoryImpl(AutoDisposeRef ref, String machineUUID) + NotificationSettingsRepositoryImpl(Ref ref, String machineUUID) : _databaseService = ref.watch(moonrakerDatabaseClientProvider(machineUUID)); final MoonrakerDatabaseClient _databaseService; diff --git a/common/lib/data/repository/machine_hive_repository.dart b/common/lib/data/repository/machine_hive_repository.dart index 0ecc534db..064379ea6 100644 --- a/common/lib/data/repository/machine_hive_repository.dart +++ b/common/lib/data/repository/machine_hive_repository.dart @@ -5,6 +5,7 @@ import 'package:common/data/model/hive/machine.dart'; import 'package:hive_ce/hive.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'machine_repository.dart'; @@ -12,7 +13,7 @@ import 'machine_repository.dart'; part 'machine_hive_repository.g.dart'; @Riverpod(keepAlive: true) -MachineHiveRepository machineRepository(MachineRepositoryRef ref) => MachineHiveRepository(); +MachineHiveRepository machineRepository(Ref ref) => MachineHiveRepository(); class MachineHiveRepository implements MachineRepository { MachineHiveRepository() : _boxMachines = Hive.box('printers'); diff --git a/common/lib/data/repository/machine_settings_moonraker_repository.dart b/common/lib/data/repository/machine_settings_moonraker_repository.dart index 435293022..559685b62 100644 --- a/common/lib/data/repository/machine_settings_moonraker_repository.dart +++ b/common/lib/data/repository/machine_settings_moonraker_repository.dart @@ -3,6 +3,7 @@ * All rights reserved. */ +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../network/moonraker_database_client.dart'; @@ -12,8 +13,7 @@ import 'machine_settings_repository.dart'; part 'machine_settings_moonraker_repository.g.dart'; @riverpod -MachineSettingsRepository machineSettingsRepository( - MachineSettingsRepositoryRef ref, String machineUUID) { +MachineSettingsRepository machineSettingsRepository(Ref ref, String machineUUID) { return MachineSettingsMoonrakerRepository( ref.watch(moonrakerDatabaseClientProvider(machineUUID))); } diff --git a/common/lib/data/repository/notifications_hive_repository.dart b/common/lib/data/repository/notifications_hive_repository.dart index dda0e7b99..47ee8fa31 100644 --- a/common/lib/data/repository/notifications_hive_repository.dart +++ b/common/lib/data/repository/notifications_hive_repository.dart @@ -5,6 +5,7 @@ import 'package:common/data/model/hive/notification.dart'; import 'package:hive_ce/hive.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'notifications_repository.dart'; @@ -12,7 +13,7 @@ import 'notifications_repository.dart'; part 'notifications_hive_repository.g.dart'; @Riverpod(keepAlive: true) -NotificationsRepository notificationRepository(NotificationRepositoryRef ref) => NotificationsHiveRepository(); +NotificationsRepository notificationRepository(Ref ref) => NotificationsHiveRepository(); class NotificationsHiveRepository extends NotificationsRepository { NotificationsHiveRepository() : _box = Hive.box('notifications'); diff --git a/common/lib/data/repository/webcam_info_repository.dart b/common/lib/data/repository/webcam_info_repository.dart index 3bf43b6e6..86982514d 100644 --- a/common/lib/data/repository/webcam_info_repository.dart +++ b/common/lib/data/repository/webcam_info_repository.dart @@ -5,6 +5,7 @@ import 'package:common/data/repository/webcam_info_repository_impl.dart'; import 'package:common/util/extensions/async_ext.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../network/jrpc_client_provider.dart'; @@ -17,7 +18,7 @@ import 'webcam_info_repository_legacy.dart'; part 'webcam_info_repository.g.dart'; @riverpod -WebcamInfoRepository webcamInfoRepository(WebcamInfoRepositoryRef ref, String machineUUID) { +WebcamInfoRepository webcamInfoRepository(Ref ref, String machineUUID) { var moonrakerVersion = ref.watch(klipperProvider(machineUUID).selectAs((data) => data.moonrakerVersion)).valueOrNull; // Prior to this commit, there was a bug in moonraker that caused the webcam API to not work properly. // https://github.com/Arksine/moonraker/commit/f487de77bc4c2db154299747aefce0ed2354bbf8 diff --git a/common/lib/network/dio_provider.dart b/common/lib/network/dio_provider.dart index 876a8dc82..1032e84df 100644 --- a/common/lib/network/dio_provider.dart +++ b/common/lib/network/dio_provider.dart @@ -18,6 +18,7 @@ import 'package:dio/io.dart'; import 'package:dio_smart_retry/dio_smart_retry.dart'; import 'package:hashlib/hashlib.dart'; import 'package:hashlib_codecs/hashlib_codecs.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../exceptions/mobileraker_exception.dart'; @@ -27,7 +28,7 @@ part 'dio_provider.g.dart'; const _thirdPartyRemoteConnectionTimeout = Duration(seconds: 30); @riverpod -Dio dioClient(DioClientRef ref, String machineUUID) { +Dio dioClient(Ref ref, String machineUUID) { final clientType = ref.watch(jrpcClientTypeProvider(machineUUID)); final baseOptions = ref.watch(baseOptionsProvider(machineUUID, clientType)); @@ -47,7 +48,7 @@ Dio dioClient(DioClientRef ref, String machineUUID) { } @riverpod -BaseOptions baseOptions(BaseOptionsRef ref, String machineUUID, ClientType clientType) { +BaseOptions baseOptions(Ref ref, String machineUUID, ClientType clientType) { var machine = ref.watch(machineProvider(machineUUID)).valueOrNull; if (machine == null) { @@ -98,7 +99,7 @@ BaseOptions baseOptions(BaseOptionsRef ref, String machineUUID, ClientType clien @riverpod -Dio octoApiClient(OctoApiClientRef ref) { +Dio octoApiClient(Ref ref) { var dio = Dio(BaseOptions( baseUrl: 'https://octoeverywhere.com/api', connectTimeout: const Duration(seconds: 10), @@ -111,7 +112,7 @@ Dio octoApiClient(OctoApiClientRef ref) { } @riverpod -Dio obicoApiClient(ObicoApiClientRef ref, String baseUri) { +Dio obicoApiClient(Ref ref, String baseUri) { var dio = Dio(BaseOptions( baseUrl: baseUri, connectTimeout: const Duration(seconds: 10), diff --git a/common/lib/network/http_client_factory.dart b/common/lib/network/http_client_factory.dart index 921ec7c4a..de94654ea 100644 --- a/common/lib/network/http_client_factory.dart +++ b/common/lib/network/http_client_factory.dart @@ -9,12 +9,13 @@ import 'package:common/network/json_rpc_client.dart'; import 'package:common/util/extensions/dio_options_extension.dart'; import 'package:dio/dio.dart'; import 'package:hashlib/hashlib.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'http_client_factory.g.dart'; @Riverpod(keepAlive: true) -HttpClientFactory httpClientFactory(HttpClientFactoryRef ref) { +HttpClientFactory httpClientFactory(Ref ref) { return HttpClientFactory._(); } diff --git a/common/lib/network/jrpc_client_provider.dart b/common/lib/network/jrpc_client_provider.dart index e49525bf1..6ff159738 100644 --- a/common/lib/network/jrpc_client_provider.dart +++ b/common/lib/network/jrpc_client_provider.dart @@ -15,6 +15,7 @@ import 'package:common/util/extensions/ref_extension.dart'; import 'package:common/util/logger.dart'; import 'package:flutter/scheduler.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../service/machine_service.dart'; @@ -24,7 +25,7 @@ import 'http_client_factory.dart'; part 'jrpc_client_provider.g.dart'; @riverpod -JsonRpcClient _jsonRpcClient(_JsonRpcClientRef ref, String machineUUID, ClientType type) { +JsonRpcClient _jsonRpcClient(Ref ref, String machineUUID, ClientType type) { var machine = ref.watch(machineProvider(machineUUID)).valueOrNull; if (machine == null) { throw MobilerakerException('Machine with UUID "$machineUUID" was not found!'); @@ -52,14 +53,14 @@ JsonRpcClient _jsonRpcClient(_JsonRpcClientRef ref, String machineUUID, ClientTy } @riverpod -Stream _jsonRpcState(_JsonRpcStateRef ref, String machineUUID, ClientType type) { +Stream _jsonRpcState(Ref ref, String machineUUID, ClientType type) { JsonRpcClient activeClient = ref.watch(_jsonRpcClientProvider(machineUUID, type)); return activeClient.stateStream; } @riverpod -JsonRpcClient jrpcClient(JrpcClientRef ref, String machineUUID) { +JsonRpcClient jrpcClient(Ref ref, String machineUUID) { var providerToWatch = ref.watch(jrpcClientManagerProvider(machineUUID)); return ref.watch(providerToWatch); } @@ -160,14 +161,14 @@ class JrpcClientManager extends _$JrpcClientManager { } @riverpod -Stream jrpcClientState(JrpcClientStateRef ref, String machineUUID) { +Stream jrpcClientState(Ref ref, String machineUUID) { var jsonRpcClient = ref.watch(jrpcClientProvider(machineUUID)); return ref.watchAsSubject(_jsonRpcStateProvider(machineUUID, jsonRpcClient.clientType)); } @riverpod -ClientType jrpcClientType(JrpcClientTypeRef ref, String machineUUID) { +ClientType jrpcClientType(Ref ref, String machineUUID) { return ref.watch(jrpcClientProvider(machineUUID).select((value) => value.clientType)); } @@ -188,7 +189,7 @@ ClientType jrpcClientType(JrpcClientTypeRef ref, String machineUUID) { // }); @riverpod -JsonRpcClient jrpcClientSelected(JrpcClientSelectedRef ref) { +JsonRpcClient jrpcClientSelected(Ref ref) { var machine = ref.watch(selectedMachineProvider).value; if (machine == null) { throw const MobilerakerException('Machine was null!'); @@ -197,7 +198,7 @@ JsonRpcClient jrpcClientSelected(JrpcClientSelectedRef ref) { } @riverpod -Stream jrpcClientStateSelected(JrpcClientStateSelectedRef ref) async* { +Stream jrpcClientStateSelected(Ref ref) async* { try { Machine? machine = await ref.watch(selectedMachineProvider.future); if (machine == null) return; @@ -209,8 +210,7 @@ Stream jrpcClientStateSelected(JrpcClientStateSelectedRef ref) asyn } @riverpod -Stream> jrpcMethodEvent(JrpcMethodEventRef ref, String machineUUID, - [String method = WILDCARD_METHOD]) { +Stream> jrpcMethodEvent(Ref ref, String machineUUID, [String method = WILDCARD_METHOD]) { StreamController> streamController = StreamController.broadcast(); JsonRpcClient jsonRpcClient = ref.watch(jrpcClientProvider(machineUUID)); diff --git a/common/lib/network/moonraker_database_client.dart b/common/lib/network/moonraker_database_client.dart index 15ae50c6b..6d08fed59 100644 --- a/common/lib/network/moonraker_database_client.dart +++ b/common/lib/network/moonraker_database_client.dart @@ -17,14 +17,14 @@ import 'jrpc_client_provider.dart'; part 'moonraker_database_client.g.dart'; @riverpod -MoonrakerDatabaseClient moonrakerDatabaseClient(MoonrakerDatabaseClientRef ref, String machineUUID) => +MoonrakerDatabaseClient moonrakerDatabaseClient(Ref ref, String machineUUID) => MoonrakerDatabaseClient(ref, machineUUID); /// The DatabaseService handles interacts with moonrakers database! class MoonrakerDatabaseClient { MoonrakerDatabaseClient(this.ref, this.machineUUID) : _jsonRpcClient = ref.watch(jrpcClientProvider(machineUUID)); - final AutoDisposeRef ref; + final Ref ref; final JsonRpcClient _jsonRpcClient; final String machineUUID; diff --git a/common/lib/service/app_router.dart b/common/lib/service/app_router.dart index 8a0b51f02..cc5e03afa 100644 --- a/common/lib/service/app_router.dart +++ b/common/lib/service/app_router.dart @@ -4,6 +4,7 @@ */ import 'package:go_router/go_router.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'app_router.g.dart'; @@ -13,4 +14,4 @@ mixin RouteDefinitionMixin implements Enum { } @riverpod -Raw goRouter(GoRouterRef ref) => throw UnimplementedError(); +Raw goRouter(Ref ref) => throw UnimplementedError(); diff --git a/common/lib/service/date_format_service.dart b/common/lib/service/date_format_service.dart index 0debb4c28..1c3846b7e 100644 --- a/common/lib/service/date_format_service.dart +++ b/common/lib/service/date_format_service.dart @@ -5,12 +5,13 @@ import 'package:common/service/setting_service.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'date_format_service.g.dart'; @Riverpod(keepAlive: true) -DateFormatService dateFormatService(DateFormatServiceRef ref) { +DateFormatService dateFormatService(Ref ref) { var settingService = ref.watch(settingServiceProvider); return DateFormatService(settingService); } diff --git a/common/lib/service/firebase/analytics.dart b/common/lib/service/firebase/analytics.dart index ac356310c..0360faceb 100644 --- a/common/lib/service/firebase/analytics.dart +++ b/common/lib/service/firebase/analytics.dart @@ -4,11 +4,12 @@ */ import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'analytics.g.dart'; @Riverpod(keepAlive: true) -FirebaseAnalytics analytics(AnalyticsRef ref) { +FirebaseAnalytics analytics(Ref ref) { return FirebaseAnalytics.instance; } diff --git a/common/lib/service/firebase/auth.dart b/common/lib/service/firebase/auth.dart index 6f2f18aa6..65cbe1506 100644 --- a/common/lib/service/firebase/auth.dart +++ b/common/lib/service/firebase/auth.dart @@ -4,6 +4,7 @@ */ import 'package:firebase_auth/firebase_auth.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../util/logger.dart'; @@ -11,12 +12,12 @@ import '../../util/logger.dart'; part 'auth.g.dart'; @Riverpod(keepAlive: true) -FirebaseAuth auth(AuthRef ref) { +FirebaseAuth auth(Ref ref) { return FirebaseAuth.instance; } @Riverpod(keepAlive: true) -Stream firebaseUser(FirebaseUserRef ref) { +Stream firebaseUser(Ref ref) { var firebaseAuth = ref.watch(authProvider); ref.listenSelf((previous, next) { diff --git a/common/lib/service/firebase/firestore.dart b/common/lib/service/firebase/firestore.dart index 68635a818..502244623 100644 --- a/common/lib/service/firebase/firestore.dart +++ b/common/lib/service/firebase/firestore.dart @@ -4,11 +4,12 @@ */ import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'firestore.g.dart'; @riverpod -FirebaseFirestore firestore(FirestoreRef ref) { +FirebaseFirestore firestore(Ref ref) { return FirebaseFirestore.instance; } diff --git a/common/lib/service/firebase/remote_config.dart b/common/lib/service/firebase/remote_config.dart index 318dd363e..a349b62e7 100644 --- a/common/lib/service/firebase/remote_config.dart +++ b/common/lib/service/firebase/remote_config.dart @@ -9,19 +9,20 @@ import 'package:common/util/logger.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:flutter/foundation.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'remote_config.g.dart'; @Riverpod(keepAlive: true) -FirebaseRemoteConfig remoteConfigInstance(RemoteConfigInstanceRef ref) { +FirebaseRemoteConfig remoteConfigInstance(Ref ref) { final instance = FirebaseRemoteConfig.instance; return instance; } @Riverpod(keepAlive: true) -Stream _remoteConfigUpdateStream(_RemoteConfigUpdateStreamRef ref) async* { +Stream _remoteConfigUpdateStream(Ref ref) async* { final instance = ref.watch(remoteConfigInstanceProvider); await for (final update in instance.onConfigUpdated) { logger.i('[Remote-Config] Received update for keys: ${update.updatedKeys.join(', ')}'); @@ -41,7 +42,7 @@ Stream _remoteConfigUpdateStream(_RemoteConfigUpdateStreamRe } @riverpod -int remoteConfigInt(RemoteConfigIntRef ref, String key) { +int remoteConfigInt(Ref ref, String key) { final instance = ref.watch(remoteConfigInstanceProvider); ref.listen(_remoteConfigUpdateStreamProvider, (prev, next) { @@ -57,7 +58,7 @@ int remoteConfigInt(RemoteConfigIntRef ref, String key) { } @riverpod -String remoteConfigString(RemoteConfigStringRef ref, String key) { +String remoteConfigString(Ref ref, String key) { final instance = ref.watch(remoteConfigInstanceProvider); ref.listen(_remoteConfigUpdateStreamProvider, (prev, next) { @@ -73,7 +74,7 @@ String remoteConfigString(RemoteConfigStringRef ref, String key) { } @riverpod -bool remoteConfigBool(RemoteConfigBoolRef ref, String key) { +bool remoteConfigBool(Ref ref, String key) { final instance = ref.watch(remoteConfigInstanceProvider); ref.listen(_remoteConfigUpdateStreamProvider, (prev, next) { @@ -88,7 +89,7 @@ bool remoteConfigBool(RemoteConfigBoolRef ref, String key) { } @riverpod -DeveloperAnnouncement developerAnnouncement(DeveloperAnnouncementRef ref) { +DeveloperAnnouncement developerAnnouncement(Ref ref) { ref.keepAlive(); // var d = { diff --git a/common/lib/service/live_activity_service.dart b/common/lib/service/live_activity_service.dart index d62c0d925..b987da9a6 100644 --- a/common/lib/service/live_activity_service.dart +++ b/common/lib/service/live_activity_service.dart @@ -37,12 +37,12 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'live_activity_service.g.dart'; @Riverpod(keepAlive: true) -LiveActivities liveActivity(LiveActivityRef ref) { +LiveActivities liveActivity(Ref ref) { return LiveActivities(); } @riverpod -LiveActivityService liveActivityService(LiveActivityServiceRef ref) => LiveActivityService(ref); +LiveActivityService liveActivityService(Ref ref) => LiveActivityService(ref); class LiveActivityService { LiveActivityService(this.ref) @@ -54,7 +54,7 @@ class LiveActivityService { ref.onDispose(dispose); } - final AutoDisposeRef ref; + final Ref ref; final MachineService _machineService; final SettingService _settingsService; final LiveActivities _liveActivityAPI; diff --git a/common/lib/service/live_activity_service_v2.dart b/common/lib/service/live_activity_service_v2.dart index b8e7ce88f..87d37bc5a 100644 --- a/common/lib/service/live_activity_service_v2.dart +++ b/common/lib/service/live_activity_service_v2.dart @@ -39,7 +39,7 @@ part 'live_activity_service_v2.g.dart'; const int PRINTER_DATA_REFRESH_INTERVAL = 5; // SECONDS @riverpod -LiveActivityServiceV2 v2LiveActivity(V2LiveActivityRef ref) { +LiveActivityServiceV2 v2LiveActivity(Ref ref) { ref.keepAlive(); return LiveActivityServiceV2(ref); } @@ -53,7 +53,7 @@ class LiveActivityServiceV2 { ref.onDispose(dispose); } - final AutoDisposeRef ref; + final Ref ref; final MachineService _machineService; final SettingService _settingsService; final LiveActivities _liveActivityAPI; diff --git a/common/lib/service/machine_service.dart b/common/lib/service/machine_service.dart index c131959a2..e3c1b23f5 100644 --- a/common/lib/service/machine_service.dart +++ b/common/lib/service/machine_service.dart @@ -58,13 +58,13 @@ import 'setting_service.dart'; part 'machine_service.g.dart'; @riverpod -MachineService machineService(MachineServiceRef ref) { +MachineService machineService(Ref ref) { ref.keepAlive(); return MachineService(ref); } @riverpod -Future machine(MachineRef ref, String uuid) async { +Future machine(Ref ref, String uuid) async { /// Using keepAliveFor ensures that the machineProvider remains active until all users of this provider are disposed. /// While ensuring that it eventually gets disposed. ref.keepAliveFor(); @@ -77,75 +77,81 @@ Future machine(MachineRef ref, String uuid) async { } @riverpod -Future> allMachines(AllMachinesRef ref) async { - ref.listenSelf((previous, next) { - next.whenData((value) => logger.i('Updated allMachinesProvider: ${value.map((e) => e.logName).join()}')); - }); +class AllMachines extends _$AllMachines { + @override + FutureOr> build() async { + listenSelf((previous, next) { + next.whenData((value) => logger.i('Updated allMachinesProvider: ${value.map((e) => e.logName).join()}')); + }); - logger.i('Received fetchAll'); + logger.i('Received fetchAll'); - var settingService = ref.watch(settingServiceProvider); - // Since the machineServiceProvider invalidates this provider, we need to use read. This is fine since machineServiceProvider is a service and non reactive! - var machines = await ref.read(machineServiceProvider).fetchAll(); - final ordering = ref.watch(stringListSettingProvider(UtilityKeys.machineOrdering, [])); + var settingService = ref.watch(settingServiceProvider); + // Since the machineServiceProvider invalidates this provider, we need to use read. This is fine since machineServiceProvider is a service and non reactive! + var machines = await ref.read(machineServiceProvider).fetchAll(); + final ordering = ref.watch(stringListSettingProvider(UtilityKeys.machineOrdering, [])); - logger.i('Received ordering $ordering'); - machines = machines.sorted((a, b) { - final aOrder = ordering.indexOf(a.uuid).let((it) => it == -1 ? double.infinity : it); - final bOrder = ordering.indexOf(b.uuid).let((it) => it == -1 ? double.infinity : it); - return aOrder.compareTo(bOrder); - }); + logger.i('Received ordering $ordering'); + machines = machines.sorted((a, b) { + final aOrder = ordering.indexOf(a.uuid).let((it) => it == -1 ? double.infinity : it); + final bOrder = ordering.indexOf(b.uuid).let((it) => it == -1 ? double.infinity : it); + return aOrder.compareTo(bOrder); + }); - var isSupporter = await ref.watch(isSupporterAsyncProvider.future); - logger.i('Received isSupporter $isSupporter'); - var maxNonSupporterMachines = ref.watch(remoteConfigIntProvider('non_suporters_max_printers')); - logger.i('Max allowed machines for non Supporters is $maxNonSupporterMachines'); - if (isSupporter) { - await settingService.delete(UtilityKeys.nonSupporterMachineCleanup); - } + var isSupporter = await ref.watch(isSupporterAsyncProvider.future); + logger.i('Received isSupporter $isSupporter'); + var maxNonSupporterMachines = ref.watch(remoteConfigIntProvider('non_suporters_max_printers')); + logger.i('Max allowed machines for non Supporters is $maxNonSupporterMachines'); + if (isSupporter) { + await settingService.delete(UtilityKeys.nonSupporterMachineCleanup); + } - if (isSupporter || maxNonSupporterMachines <= 0 || machines.length <= maxNonSupporterMachines) { - return machines; - } + if (isSupporter || maxNonSupporterMachines <= 0 || machines.length <= maxNonSupporterMachines) { + return machines; + } - DateTime? cleanupDate = settingService.read(UtilityKeys.nonSupporterMachineCleanup, null); + DateTime? cleanupDate = settingService.read(UtilityKeys.nonSupporterMachineCleanup, null); - if (cleanupDate == null) { - cleanupDate = DateTime.now().add(const Duration(days: 7)); - cleanupDate = DateTime(cleanupDate.year, cleanupDate.month, cleanupDate.day); - logger.i('Writing nonSupporter machine cleanup date $cleanupDate'); - settingService.write(UtilityKeys.nonSupporterMachineCleanup, cleanupDate); - return machines; - } + if (cleanupDate == null) { + cleanupDate = DateTime.now().add(const Duration(days: 7)); + cleanupDate = DateTime(cleanupDate.year, cleanupDate.month, cleanupDate.day); + logger.i('Writing nonSupporter machine cleanup date $cleanupDate'); + settingService.write(UtilityKeys.nonSupporterMachineCleanup, cleanupDate); + return machines; + } + + if (cleanupDate.isBefore(DateTime.now())) { + // if (cleanupDate.difference(DateTime.now()).inDays >= 0) { + var oLen = machines.length; + machines = machines.sublist(0, maxNonSupporterMachines); + logger.i( + 'Hiding machines from user since he is not a supporter! Original len was $oLen, new length is ${machines.length}'); + return machines; + } - if (cleanupDate.isBefore(DateTime.now())) { - // if (cleanupDate.difference(DateTime.now()).inDays >= 0) { - var oLen = machines.length; - machines = machines.sublist(0, maxNonSupporterMachines); - logger.i( - 'Hiding machines from user since he is not a supporter! Original len was $oLen, new length is ${machines.length}'); return machines; } - - return machines; } @riverpod -Future> hiddenMachines(HiddenMachinesRef ref) async { - ref.listenSelf((previous, next) { - next.whenData((value) => logger.i('Updated hiddenMachinesProvider: ${value.map((e) => e.logName).join()}')); - }); +class HiddenMachines extends _$HiddenMachines { + @override + FutureOr> build() async { + listenSelf((previous, next) { + next.whenData((value) => logger.i('Updated hiddenMachinesProvider: ${value.map((e) => e.logName).join()}')); + }); - var machinesAvailableToUser = await ref.watch(allMachinesProvider.selectAsync((data) => data.map((e) => e.uuid))); - // Since the machineServiceProvider invalidates this provider, we need to use read. This is fine since machineServiceProvider is a service and non reactive! - var actualStoredMachines = await ref.read(machineServiceProvider).fetchAll(); - var hiddenMachines = actualStoredMachines.where((e) => !machinesAvailableToUser.contains(e.uuid)); + var machinesAvailableToUser = await ref.watch(allMachinesProvider.selectAsync((data) => data.map((e) => e.uuid))); + // Since the machineServiceProvider invalidates this provider, we need to use read. This is fine since machineServiceProvider is a service and non reactive! + var actualStoredMachines = await ref.read(machineServiceProvider).fetchAll(); + var hiddenMachines = actualStoredMachines.where((e) => !machinesAvailableToUser.contains(e.uuid)); - return hiddenMachines.toList(growable: false); + return hiddenMachines.toList(growable: false); + } } @riverpod -Stream machineSettings(MachineSettingsRef ref, String machineUUID) async* { +Stream machineSettings(Ref ref, String machineUUID) async* { ref.keepAliveFor(); // Just ensure we have a machine to prevent errors while we dispose the machine/on remove the machine. @@ -171,7 +177,7 @@ Stream machineSettings(MachineSettingsRef ref, String machineUU } @riverpod -Stream selectedMachineSettings(SelectedMachineSettingsRef ref) async* { +Stream selectedMachineSettings(Ref ref) async* { try { var machine = await ref.watch(selectedMachineProvider.future); if (machine == null) return; @@ -194,7 +200,7 @@ class MachineService { ref.onDispose(dispose); } - final AutoDisposeRef ref; + final Ref ref; final MachineHiveRepository _machineRepo; final SelectedMachineService _selectedMachineService; final SettingService _settingService; diff --git a/common/lib/service/misc_providers.dart b/common/lib/service/misc_providers.dart index cb6c90a99..47eadf889 100644 --- a/common/lib/service/misc_providers.dart +++ b/common/lib/service/misc_providers.dart @@ -7,6 +7,7 @@ import 'package:flutter/scheduler.dart'; import 'package:network_info_plus/network_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../util/logger.dart'; @@ -14,17 +15,17 @@ import '../util/logger.dart'; part 'misc_providers.g.dart'; @Riverpod(keepAlive: true) -NetworkInfo networkInfoService(NetworkInfoServiceRef ref) { +NetworkInfo networkInfoService(Ref ref) { return NetworkInfo(); } @Riverpod(keepAlive: true) -Future versionInfo(VersionInfoRef _) { +Future versionInfo(Ref _) { return PackageInfo.fromPlatform(); } @riverpod -Future permissionStatus(PermissionStatusRef ref, Permission permission) async { +Future permissionStatus(Ref ref, Permission permission) async { var status = await permission.status; logger.i('Permission $permission is $status'); @@ -32,7 +33,7 @@ Future permissionStatus(PermissionStatusRef ref, Permission pe } @riverpod -Future permissionServiceStatus(PermissionServiceStatusRef ref, PermissionWithService permission) async { +Future permissionServiceStatus(Ref ref, PermissionWithService permission) async { var status = await permission.serviceStatus; logger.i('Permission $permission serviceStatus is $status'); diff --git a/common/lib/service/moonraker/announcement_service.dart b/common/lib/service/moonraker/announcement_service.dart index ea723fd5b..4b1da04eb 100644 --- a/common/lib/service/moonraker/announcement_service.dart +++ b/common/lib/service/moonraker/announcement_service.dart @@ -18,12 +18,12 @@ import '../../network/jrpc_client_provider.dart'; part 'announcement_service.g.dart'; @riverpod -AnnouncementService announcementService(AnnouncementServiceRef ref, String machineUUID) { +AnnouncementService announcementService(Ref ref, String machineUUID) { return AnnouncementService(ref, machineUUID); } @riverpod -Stream> announcement(AnnouncementRef ref, String machineUUID) { +Stream> announcement(Ref ref, String machineUUID) { return ref.watch(announcementServiceProvider(machineUUID)).announcementNotificationStream; } @@ -31,8 +31,7 @@ Stream> announcement(AnnouncementRef ref, String machine /// For more information check out /// 1. https://moonraker.readthedocs.io/en/latest/web_api/#announcement-apis class AnnouncementService { - AnnouncementService(AutoDisposeRef ref, String machineUUID) - : _jRpcClient = ref.watch(jrpcClientProvider(machineUUID)) { + AnnouncementService(Ref ref, String machineUUID) : _jRpcClient = ref.watch(jrpcClientProvider(machineUUID)) { ref.onDispose(dispose); _jRpcClient.addMethodListener(_onNotifyAnnouncementUpdate, 'notify_announcement_update'); _jRpcClient.addMethodListener(_onNotifyAnnouncementDismissed, 'notify_announcement_dismissed'); diff --git a/common/lib/service/moonraker/file_service.dart b/common/lib/service/moonraker/file_service.dart index dcb8a54bb..e6d0113d9 100644 --- a/common/lib/service/moonraker/file_service.dart +++ b/common/lib/service/moonraker/file_service.dart @@ -86,7 +86,7 @@ class FolderContentWrapper with _$FolderContentWrapper { } @riverpod -CacheManager httpCacheManager(HttpCacheManagerRef ref, String machineUUID) { +CacheManager httpCacheManager(Ref ref, String machineUUID) { final clientType = ref.watch(jrpcClientTypeProvider(machineUUID)); final baseOptions = ref.watch(baseOptionsProvider(machineUUID, clientType)); final httpClientFactory = ref.watch(httpClientFactoryProvider); @@ -105,7 +105,7 @@ CacheManager httpCacheManager(HttpCacheManagerRef ref, String machineUUID) { } @riverpod -Uri? previewImageUri(PreviewImageUriRef ref) { +Uri? previewImageUri(Ref ref) { var machine = ref.watch(selectedMachineProvider).valueOrFullNull; if (machine == null) return null; @@ -116,7 +116,7 @@ Uri? previewImageUri(PreviewImageUriRef ref) { } @riverpod -Map previewImageHttpHeader(PreviewImageHttpHeaderRef ref) { +Map previewImageHttpHeader(Ref ref) { var machine = ref.watch(selectedMachineProvider).valueOrFullNull; if (machine == null) return {}; @@ -125,7 +125,7 @@ Map previewImageHttpHeader(PreviewImageHttpHeaderRef ref) { } @riverpod -FileService fileService(FileServiceRef ref, String machineUUID) { +FileService fileService(Ref ref, String machineUUID) { var dio = ref.watch(dioClientProvider(machineUUID)); var jsonRpcClient = ref.watch(jrpcClientProvider(machineUUID)); @@ -133,12 +133,12 @@ FileService fileService(FileServiceRef ref, String machineUUID) { } @riverpod -Stream _rawFileNotifications(_RawFileNotificationsRef ref, String machineUUID, [String? path]) { +Stream _rawFileNotifications(Ref ref, String machineUUID, [String? path]) { return ref.watch(fileServiceProvider(machineUUID)).fileNotificationStream; } @riverpod -Stream fileNotifications(FileNotificationsRef ref, String machineUUID, [String? path]) { +Stream fileNotifications(Ref ref, String machineUUID, [String? path]) { StreamController streamController = StreamController(); ref.onDispose(streamController.close); @@ -176,12 +176,12 @@ Stream fileNotifications(FileNotificationsRef ref, String ma } @riverpod -FileService fileServiceSelected(FileServiceSelectedRef ref) { +FileService fileServiceSelected(Ref ref) { return ref.watch(fileServiceProvider(ref.watch(selectedMachineProvider).requireValue!.uuid)); } @riverpod -Stream fileNotificationsSelected(FileNotificationsSelectedRef ref) async* { +Stream fileNotificationsSelected(Ref ref) async* { ref.keepAliveFor(); try { var machine = await ref.watch(selectedMachineProvider.future); @@ -193,7 +193,7 @@ Stream fileNotificationsSelected(FileNotificationsSelectedRe } @riverpod -Future fileApiResponse(FileApiResponseRef ref, String machineUUID, String path) async { +Future fileApiResponse(Ref ref, String machineUUID, String path) async { ref.keepAliveFor(); // Invalidation of the cache is done by the fileNotificationsProvider ref.listen(fileNotificationsProvider(machineUUID, path), (prev, next) => next.whenData((d) => ref.invalidateSelf())); @@ -203,8 +203,8 @@ Future fileApiResponse(FileApiResponseRef ref, String mach } @riverpod -Future moonrakerFolderContent( - MoonrakerFolderContentRef ref, String machineUUID, String path, SortConfiguration sortConfig) async { +Future moonrakerFolderContent(Ref ref, String machineUUID, String path, + SortConfiguration sortConfig) async { ref.keepAliveFor(); ref.listen(fileNotificationsProvider(machineUUID, path), (prev, next) => next.whenData((d) => ref.invalidateSelf())); // await Future.delayed(const Duration(milliseconds: 5000)); @@ -225,7 +225,7 @@ Future moonrakerFolderContent( /// 1. https://moonraker.readthedocs.io/en/latest/web_api/#file-operations /// 2. https://moonraker.readthedocs.io/en/latest/web_api/#file-list-changed class FileService { - FileService(AutoDisposeRef ref, this._machineUUID, this._jRpcClient, this._dio) + FileService(Ref ref, this._machineUUID, this._jRpcClient, this._dio) : _downloadReceiverPortName = 'downloadFilePort-${_machineUUID.hashCode}', _apiRequestTimeout = _jRpcClient.timeout > const Duration(seconds: 30) ? _jRpcClient.timeout : const Duration(seconds: 30) { diff --git a/common/lib/service/moonraker/klipper_system_service.dart b/common/lib/service/moonraker/klipper_system_service.dart index 46fd61fef..c177633b2 100644 --- a/common/lib/service/moonraker/klipper_system_service.dart +++ b/common/lib/service/moonraker/klipper_system_service.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'package:common/data/dto/server/klipper_system_info.dart'; import 'package:common/util/extensions/ref_extension.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../data/dto/server/service_status.dart'; @@ -17,9 +18,9 @@ part 'klipper_system_service.g.dart'; /// Shorthand to get the status of a service @riverpod -Future systemServiceStatus(SystemServiceStatusRef ref, String machineUUID, String service) async { +Future systemServiceStatus(Ref ref, String machineUUID, String service) async { var serviceState = - await ref.watch(klipperSystemInfoProvider(machineUUID).selectAsync((data) => data.serviceState[service])); + await ref.watch(klippySystemInfoProvider(machineUUID).selectAsync((data) => data.serviceState[service])); if (serviceState == null) { throw ArgumentError('Service "$service" not found in KlipperSystemInfo'); @@ -28,18 +29,20 @@ Future systemServiceStatus(SystemServiceStatusRef ref, String mac } @riverpod -Future klipperSystemInfo(KlipperSystemInfoRef ref, String machineUUID) async { - var client = ref.watch(jrpcClientProvider(machineUUID)); +class KlippySystemInfo extends _$KlippySystemInfo { + @override + FutureOr build(String machineUUID) async { + var client = ref.watch(jrpcClientProvider(machineUUID)); - var response = await client.sendJRpcMethod('machine.system_info'); + var response = await client.sendJRpcMethod('machine.system_info'); - // https://moonraker.readthedocs.io/en/latest/web_api/#service-state-changed - ref.listen(jrpcMethodEventProvider(machineUUID, 'notify_service_state_changed'), (previous, next) { - if (next.isLoading) return; - if (next.hasError) return; + // https://moonraker.readthedocs.io/en/latest/web_api/#service-state-changed + ref.listen(jrpcMethodEventProvider(machineUUID, 'notify_service_state_changed'), (previous, next) { + if (next.isLoading) return; + if (next.hasError) return; - var rawMessage = next.requireValue; - /* + var rawMessage = next.requireValue; + /* [ { "klipper": { @@ -50,27 +53,28 @@ Future klipperSystemInfo(KlipperSystemInfoRef ref, String mac ] */ - Map rawServiceUpdates = rawMessage['params'][0]; - var changedServices = rawServiceUpdates - .map((k, e) => MapEntry(k, ServiceStatus.fromJson({'name': k, ...(e as Map)}))); + Map rawServiceUpdates = rawMessage['params'][0]; + var changedServices = rawServiceUpdates + .map((k, e) => MapEntry(k, ServiceStatus.fromJson({'name': k, ...(e as Map)}))); - ref.state = ref.state.whenData( - (value) => value.copyWith(serviceState: Map.unmodifiable({...value.serviceState, ...changedServices}))); - }); + state = state.whenData( + (value) => value.copyWith(serviceState: Map.unmodifiable({...value.serviceState, ...changedServices}))); + }); - var json = response.result['system_info']; - KlipperSystemInfo info = KlipperSystemInfo.fromJson(json); - return info; + var json = response.result['system_info']; + KlipperSystemInfo info = KlipperSystemInfo.fromJson(json); + return info; + } } @riverpod -Stream selectedKlipperSystemInfo(SelectedKlipperSystemInfoRef ref) async* { +Stream selectedKlipperSystemInfo(Ref ref) async* { ref.keepAliveFor(); try { var machine = await ref.watch(selectedMachineProvider.future); if (machine == null) return; - yield await ref.watch(klipperSystemInfoProvider(machine.uuid).future); + yield await ref.watch(klippySystemInfoProvider(machine.uuid).future); } on StateError catch (_) { // Just catch it. It is expected that the future/where might not complete! } diff --git a/common/lib/service/moonraker/klippy_service.dart b/common/lib/service/moonraker/klippy_service.dart index b57b3ae01..a7a76a6d5 100644 --- a/common/lib/service/moonraker/klippy_service.dart +++ b/common/lib/service/moonraker/klippy_service.dart @@ -26,22 +26,22 @@ import '../selected_machine_service.dart'; part 'klippy_service.g.dart'; @riverpod -KlippyService klipperService(KlipperServiceRef ref, String machineUUID) { +KlippyService klipperService(Ref ref, String machineUUID) { return KlippyService(ref, machineUUID); } @riverpod -Stream klipper(KlipperRef ref, String machineUUID) { +Stream klipper(Ref ref, String machineUUID) { return ref.watch(klipperServiceProvider(machineUUID)).klipperStream; } @riverpod -KlippyService klipperServiceSelected(KlipperServiceSelectedRef ref) { +KlippyService klipperServiceSelected(Ref ref) { return ref.watch(klipperServiceProvider(ref.watch(selectedMachineProvider).requireValue!.uuid)); } @riverpod -Stream klipperSelected(KlipperSelectedRef ref) async* { +Stream klipperSelected(Ref ref) async* { try { var machine = await ref.watch(selectedMachineProvider.future); if (machine == null) return; @@ -79,7 +79,7 @@ class KlippyService { }, fireImmediately: true); } - final AutoDisposeRef ref; + final Ref ref; final JsonRpcClient _jRpcClient; diff --git a/common/lib/service/moonraker/power_service.dart b/common/lib/service/moonraker/power_service.dart index af62b983e..fbb05d75e 100644 --- a/common/lib/service/moonraker/power_service.dart +++ b/common/lib/service/moonraker/power_service.dart @@ -19,13 +19,13 @@ import '../../network/jrpc_client_provider.dart'; part 'power_service.g.dart'; @riverpod -PowerService powerService(PowerServiceRef ref, String machineUUID) { +PowerService powerService(Ref ref, String machineUUID) { var jsonRpcClient = ref.watch(jrpcClientProvider(machineUUID)); return PowerService(ref, jsonRpcClient, machineUUID); } @riverpod -Stream> powerDevices(PowerDevicesRef ref, String machineUUID) { +Stream> powerDevices(Ref ref, String machineUUID) { return ref.watch(powerServiceProvider(machineUUID)).devices; } @@ -34,7 +34,7 @@ Stream> powerDevices(PowerDevicesRef ref, String machineUUID) /// For more information check out /// 1. https://moonraker.readthedocs.io/en/latest/web_api/#power-apis class PowerService { - PowerService(AutoDisposeRef ref, this._jRpcClient, String machineUUID) { + PowerService(Ref ref, this._jRpcClient, String machineUUID) { ref.onDispose(dispose); _jRpcClient.addMethodListener(_onPowerChanged, 'notify_power_changed'); ref.listen(jrpcClientStateProvider(machineUUID), (previous, next) { diff --git a/common/lib/service/moonraker/printer_service.dart b/common/lib/service/moonraker/printer_service.dart index abbbfc021..d2b7a0235 100644 --- a/common/lib/service/moonraker/printer_service.dart +++ b/common/lib/service/moonraker/printer_service.dart @@ -2,7 +2,6 @@ * Copyright (c) 2023-2024. Patrick Schmidt. * All rights reserved. */ - import 'dart:async'; import 'dart:convert'; import 'dart:math'; @@ -40,50 +39,64 @@ import 'klippy_service.dart'; part 'printer_service.g.dart'; @riverpod -PrinterService printerService(PrinterServiceRef ref, String machineUUID) { +PrinterService printerService(Ref ref, String machineUUID) { return PrinterService(ref, machineUUID); } +// Kinda "hacky" but the only way to keep the name PrinterProvider without a conflict of the Printer name.. +@ProviderFor(PrinterNotifier) +const printerProvider = printerNotifierProvider; + @riverpod -Stream printer(PrinterRef ref, String machineUUID) { - // ref.keepAlive(); - var printerService = ref.watch(printerServiceProvider(machineUUID)); - ref.listenSelf((previous, next) { - final previousFileName = previous?.valueOrNull?.print.filename; - final nextFileName = next.valueOrNull?.print.filename; - // The 2nd case is to cover rare race conditions where a printer update was issued at the same time as this code was executed - if (previousFileName != nextFileName || - next.hasValue && - (nextFileName?.isNotEmpty == true && next.value?.currentFile == null || - nextFileName?.isEmpty == true && next.value?.currentFile != null)) { - printerService.updateCurrentFile(nextFileName).ignore(); - } +class PrinterNotifier extends _$PrinterNotifier { + @override + Stream build(String machineUUID) { + // ref.keepAlive(); + var printerService = ref.watch(printerServiceProvider(machineUUID)); + listenSelf((previous, next) { + final previousFileName = previous?.valueOrNull?.print.filename; + final nextFileName = next.valueOrNull?.print.filename; + // The 2nd case is to cover rare race conditions where a printer update was issued at the same time as this code was executed + if (previousFileName != nextFileName || + next.hasValue && + (nextFileName?.isNotEmpty == true && next.value?.currentFile == null || + nextFileName?.isEmpty == true && next.value?.currentFile != null)) { + printerService.updateCurrentFile(nextFileName).ignore(); + } - final prevMessage = previous?.valueOrNull?.print.message; - final nextMessage = next.valueOrNull?.print.message; - if (prevMessage != nextMessage && nextMessage?.isNotEmpty == true) { - ref.read(snackBarServiceProvider).show(SnackBarConfig( - type: SnackbarType.warning, - title: 'Klippy-Message', - message: nextMessage, - )); - } - }); - return printerService.printerStream; + final prevMessage = previous?.valueOrNull?.print.message; + final nextMessage = next.valueOrNull?.print.message; + if (prevMessage != nextMessage && nextMessage?.isNotEmpty == true) { + ref.read(snackBarServiceProvider).show(SnackBarConfig( + type: SnackbarType.warning, + title: 'Klippy-Message', + message: nextMessage, + )); + } + }); + return printerService.printerStream; + } +} + +final class PrinterPreviewNotifier extends PrinterNotifier { + @override + Stream build(String machineUUID) async* { + yield PrinterBuilder.preview().build(); + } } @riverpod -Future> printerAvailableCommands(PrinterAvailableCommandsRef ref, String machineUUID) async { +Future> printerAvailableCommands(Ref ref, String machineUUID) async { return ref.watch(printerServiceProvider(machineUUID)).gcodeHelp(); } @riverpod -PrinterService printerServiceSelected(PrinterServiceSelectedRef ref) { +PrinterService printerServiceSelected(Ref ref) { return ref.watch(printerServiceProvider(ref.watch(selectedMachineProvider).requireValue!.uuid)); } @riverpod -Stream printerSelected(PrinterSelectedRef ref) async* { +Stream printerSelected(Ref ref) async* { try { var machine = await ref.watch(selectedMachineProvider.future); if (machine == null) return; @@ -95,7 +108,7 @@ Stream printerSelected(PrinterSelectedRef ref) async* { } class PrinterService { - PrinterService(AutoDisposeRef ref, this.ownerUUID) + PrinterService(Ref ref, this.ownerUUID) : _jRpcClient = ref.watch(jrpcClientProvider(ownerUUID)), _fileService = ref.watch(fileServiceProvider(ownerUUID)), _snackBarService = ref.watch(snackBarServiceProvider), diff --git a/common/lib/service/moonraker/webcam_service.dart b/common/lib/service/moonraker/webcam_service.dart index 2a53ab101..d029058f1 100644 --- a/common/lib/service/moonraker/webcam_service.dart +++ b/common/lib/service/moonraker/webcam_service.dart @@ -18,12 +18,12 @@ import '../../network/jrpc_client_provider.dart'; part 'webcam_service.g.dart'; @riverpod -WebcamService webcamService(WebcamServiceRef ref, String machineUUID) { +WebcamService webcamService(Ref ref, String machineUUID) { return WebcamService(ref, machineUUID); } @riverpod -Stream> allWebcamInfos(AllWebcamInfosRef ref, String machineUUID) async* { +Stream> allWebcamInfos(Ref ref, String machineUUID) async* { final jrpcState = await ref.watch(jrpcClientStateProvider(machineUUID).future); if (jrpcState != ClientState.connected) return; @@ -34,7 +34,7 @@ Stream> allWebcamInfos(AllWebcamInfosRef ref, String machineUUI } @riverpod -Future> allSupportedWebcamInfos(AllSupportedWebcamInfosRef ref, String machineUUID) async { +Future> allSupportedWebcamInfos(Ref ref, String machineUUID) async { return (await ref.watch(allWebcamInfosProvider(machineUUID).future)) .where((element) => element.service.supported) .toList(growable: false); @@ -48,7 +48,7 @@ class WebcamService { : _webcamInfoRepository = ref.watch(webcamInfoRepositoryProvider(machineUUID)); final String machineUUID; - final AutoDisposeRef ref; + final Ref ref; final WebcamInfoRepository _webcamInfoRepository; Future> listWebcamInfos() async { diff --git a/common/lib/service/notification_service.dart b/common/lib/service/notification_service.dart index 019fd2825..0dd5657f8 100644 --- a/common/lib/service/notification_service.dart +++ b/common/lib/service/notification_service.dart @@ -32,13 +32,13 @@ import 'live_activity_service_v2.dart'; part 'notification_service.g.dart'; @Riverpod(keepAlive: true) -AwesomeNotifications awesomeNotification(AwesomeNotificationRef ref) => AwesomeNotifications(); +AwesomeNotifications awesomeNotification(Ref ref) => AwesomeNotifications(); @riverpod -AwesomeNotificationsFcm awesomeNotificationFcm(AwesomeNotificationFcmRef ref) => AwesomeNotificationsFcm(); +AwesomeNotificationsFcm awesomeNotificationFcm(Ref ref) => AwesomeNotificationsFcm(); @riverpod -NotificationService notificationService(NotificationServiceRef ref) { +NotificationService notificationService(Ref ref) { ref.keepAlive(); var notificationService = NotificationService(ref); ref.onDispose(notificationService.dispose); @@ -46,7 +46,7 @@ NotificationService notificationService(NotificationServiceRef ref) { } @riverpod -Future fcmToken(FcmTokenRef ref) async { +Future fcmToken(Ref ref) async { // Need to use read on the notificationService to prevent a circular dependency, this is fine because the service is kept alive anyway. var notificationService = ref.read(notificationServiceProvider); await notificationService.initialized; @@ -67,7 +67,7 @@ class NotificationService { _liveActivityServicev2 = _ref.watch(v2LiveActivityProvider), _notifyFCM = _ref.watch(awesomeNotificationFcmProvider); - final AutoDisposeRef _ref; + final Ref _ref; final MachineService _machineService; final SettingService _settingsService; final AwesomeNotifications _notifyAPI; diff --git a/common/lib/service/obico/obico_tunnel_service.dart b/common/lib/service/obico/obico_tunnel_service.dart index 4518f905b..e2fdee77d 100644 --- a/common/lib/service/obico/obico_tunnel_service.dart +++ b/common/lib/service/obico/obico_tunnel_service.dart @@ -19,7 +19,7 @@ import '../../util/logger.dart'; part 'obico_tunnel_service.g.dart'; @riverpod -ObicoTunnelService obicoTunnelService(ObicoTunnelServiceRef ref, [Uri? uri]) { +ObicoTunnelService obicoTunnelService(Ref ref, [Uri? uri]) { uri ??= Uri( scheme: 'https', host: 'app.obico.io', @@ -29,7 +29,7 @@ ObicoTunnelService obicoTunnelService(ObicoTunnelServiceRef ref, [Uri? uri]) { } class ObicoTunnelService { - ObicoTunnelService(AutoDisposeRef ref, Uri uri) + ObicoTunnelService(Ref ref, Uri uri) : _obicoUri = uri, _dio = ref.read(obicoApiClientProvider(uri.toString())); diff --git a/common/lib/service/octoeverywhere/app_connection_service.dart b/common/lib/service/octoeverywhere/app_connection_service.dart index 0d37d91bd..8ce0725d6 100644 --- a/common/lib/service/octoeverywhere/app_connection_service.dart +++ b/common/lib/service/octoeverywhere/app_connection_service.dart @@ -18,12 +18,12 @@ import '../../network/dio_provider.dart'; part 'app_connection_service.g.dart'; @riverpod -AppConnectionService appConnectionService(AppConnectionServiceRef ref) { +AppConnectionService appConnectionService(Ref ref) { return AppConnectionService(ref); } class AppConnectionService { - AppConnectionService(AutoDisposeRef ref) : _dio = ref.watch(octoApiClientProvider); + AppConnectionService(Ref ref) : _dio = ref.watch(octoApiClientProvider); final Dio _dio; diff --git a/common/lib/service/octoeverywhere/gadget_service.dart b/common/lib/service/octoeverywhere/gadget_service.dart index 2f1a03289..f34f4c926 100644 --- a/common/lib/service/octoeverywhere/gadget_service.dart +++ b/common/lib/service/octoeverywhere/gadget_service.dart @@ -18,12 +18,12 @@ import '../../network/dio_provider.dart'; part 'gadget_service.g.dart'; @riverpod -GadgetService gadgetService(GadgetServiceRef ref) { +GadgetService gadgetService(Ref ref) { return GadgetService(ref); } @riverpod -Future gadgetStatus(GadgetStatusRef ref, String appToken) async { +Future gadgetStatus(Ref ref, String appToken) async { var gadgetService = ref.watch(gadgetServiceProvider); createTimer() => Timer(const Duration(seconds: 10), () => ref.invalidateSelf()); @@ -53,7 +53,7 @@ Future gadgetStatus(GadgetStatusRef ref, String appToken) async { } class GadgetService { - GadgetService(AutoDisposeRef ref) : _dio = ref.watch(octoApiClientProvider); + GadgetService(Ref ref) : _dio = ref.watch(octoApiClientProvider); final Dio _dio; diff --git a/common/lib/service/payment_service.dart b/common/lib/service/payment_service.dart index 673179f2d..76b90f389 100644 --- a/common/lib/service/payment_service.dart +++ b/common/lib/service/payment_service.dart @@ -23,54 +23,60 @@ import 'ui/snackbar_service_interface.dart'; part 'payment_service.g.dart'; +@ProviderFor(CustomerInfoNotifier) +final customerInfoProvider = customerInfoNotifierProvider; + @Riverpod(keepAlive: true) Future customerInfo(CustomerInfoRef ref) async { try { var customerInfo = await Purchases.getCustomerInfo(); logger.i('Got customerInfo: $customerInfo'); - checkForExpired() async { - logger.i('Checking for expired subs!'); - var v = ref.state; - var now = DateTime.now(); - if (v.hasValue) { - var hasExpired = v.requireValue.entitlements.active.values - .any((ent) => ent.expirationDate != null && DateTime.tryParse(ent.expirationDate!)?.isBefore(now) == true); - if (hasExpired) { - logger.i('Found expired Entitlement, force refresh!'); - ref.state = await AsyncValue.guard(() async { - await Purchases.invalidateCustomerInfoCache(); - return Purchases.getCustomerInfo(); - }); - // ref.state = AsyncValue.guard(() => ) + checkForExpired() async { + logger.i('Checking for expired subs!'); + var curUserInfo = state; + var now = DateTime.now(); + if (curUserInfo.hasValue) { + var hasExpired = curUserInfo.requireValue.entitlements.active.values.any( + (ent) => ent.expirationDate != null && DateTime.tryParse(ent.expirationDate!)?.isBefore(now) == true); + if (hasExpired) { + logger.i('Found expired Entitlement, force refresh!'); + state = await AsyncValue.guard(() async { + await Purchases.invalidateCustomerInfoCache(); + return Purchases.getCustomerInfo(); + }); + // ref.state = AsyncValue.guard(() => ) + } } } - } - ref.onAddListener(checkForExpired); - ref.onResume(checkForExpired); - logger.i('RCat ID: ${customerInfo.originalAppUserId}'); + ref.onAddListener(checkForExpired); + ref.onResume(checkForExpired); + logger.i('RCat ID: ${customerInfo.originalAppUserId}'); - return customerInfo; - } on PlatformException catch (e, s) { - logger.w('Could not fetch customer info. Platform code: ${e.code}!', e, s); - return const CustomerInfo(EntitlementInfos({}, {}), {}, [], [], [], "", "", {}, ""); + return customerInfo; + } on PlatformException catch (e, s) { + logger.w('Could not fetch customer info. Platform code: ${e.code}!', e, s); + return const CustomerInfo(EntitlementInfos({}, {}), {}, [], [], [], "", "", {}, ""); + } } } @Riverpod(keepAlive: true) -bool isSupporter(IsSupporterRef ref) { +bool isSupporter(Ref ref) { + return true; return ref.watch(isSupporterAsyncProvider).valueOrNull == true; } @Riverpod(keepAlive: true) -FutureOr isSupporterAsync(IsSupporterAsyncRef ref) async { +FutureOr isSupporterAsync(Ref ref) async { + return true; var customerInfo = await ref.watch(customerInfoProvider.future); return customerInfo.entitlements.active.containsKey('Supporter') == true; } @Riverpod(keepAlive: true) -PaymentService paymentService(PaymentServiceRef ref) { +PaymentService paymentService(Ref ref) { return PaymentService(ref); } @@ -80,7 +86,7 @@ class PaymentService { final SettingService _settingService; - final PaymentServiceRef _ref; + final Ref _ref; Future initialize() async { if (kDebugMode) await Purchases.setLogLevel(LogLevel.info); diff --git a/common/lib/service/selected_machine_service.dart b/common/lib/service/selected_machine_service.dart index 59b1312a4..2bf290430 100644 --- a/common/lib/service/selected_machine_service.dart +++ b/common/lib/service/selected_machine_service.dart @@ -16,12 +16,12 @@ import '../data/repository/machine_hive_repository.dart'; part 'selected_machine_service.g.dart'; @riverpod -SelectedMachineService selectedMachineService(SelectedMachineServiceRef ref) { +SelectedMachineService selectedMachineService(Ref ref) { return SelectedMachineService(ref); } @riverpod -Stream selectedMachine(SelectedMachineRef ref) { +Stream selectedMachine(Ref ref) { ref.keepAlive(); return ref.watch(selectedMachineServiceProvider).selectedMachine; } @@ -35,7 +35,7 @@ class SelectedMachineService { _init(); } - final AutoDisposeRef ref; + final Ref ref; final MachineHiveRepository _machineRepo; diff --git a/common/lib/service/setting_service.dart b/common/lib/service/setting_service.dart index ea8085472..f54562393 100644 --- a/common/lib/service/setting_service.dart +++ b/common/lib/service/setting_service.dart @@ -6,17 +6,18 @@ import 'dart:async'; import 'package:hive_ce_flutter/hive_flutter.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'setting_service.g.dart'; @Riverpod(keepAlive: true) -SettingService settingService(SettingServiceRef ref) { +SettingService settingService(Ref ref) { return SettingService(); } @riverpod -bool boolSetting(BoolSettingRef ref, KeyValueStoreKey key, [bool fallback = false]) { +bool boolSetting(Ref ref, KeyValueStoreKey key, [bool fallback = false]) { // This is a nice way to listen to changes in the settings box. // However, we might want to move this logic to the Service (Well it would just move the responsibility) var box = Hive.box('settingsbox'); @@ -26,7 +27,7 @@ bool boolSetting(BoolSettingRef ref, KeyValueStoreKey key, [bool fallback = fals } @riverpod -String? stringSetting(StringSettingRef ref, KeyValueStoreKey key, [String? fallback]) { +String? stringSetting(Ref ref, KeyValueStoreKey key, [String? fallback]) { // This is a nice way to listen to changes in the settings box. // However, we might want to move this logic to the Service (Well it would just move the responsibility) var box = Hive.box('settingsbox'); @@ -36,7 +37,7 @@ String? stringSetting(StringSettingRef ref, KeyValueStoreKey key, [String? fallb } @riverpod -int intSetting(IntSettingRef ref, KeyValueStoreKey key, [int fallback = 0]) { +int intSetting(Ref ref, KeyValueStoreKey key, [int fallback = 0]) { // This is a nice way to listen to changes in the settings box. // However, we might want to move this logic to the Service (Well it would just move the responsibility) var box = Hive.box('settingsbox'); @@ -46,7 +47,7 @@ int intSetting(IntSettingRef ref, KeyValueStoreKey key, [int fallback = 0]) { } @riverpod -double doubleSetting(DoubleSettingRef ref, KeyValueStoreKey key, [double fallback = 0.0]) { +double doubleSetting(Ref ref, KeyValueStoreKey key, [double fallback = 0.0]) { // This is a nice way to listen to changes in the settings box. // However, we might want to move this logic to the Service (Well it would just move the responsibility) var box = Hive.box('settingsbox'); @@ -56,7 +57,7 @@ double doubleSetting(DoubleSettingRef ref, KeyValueStoreKey key, [double fallbac } @riverpod -Type objectSetting(ObjectSettingRef ref, KeyValueStoreKey key, Type fallback) { +Type objectSetting(Ref ref, KeyValueStoreKey key, Type fallback) { // This is a nice way to listen to changes in the settings box. // However, we might want to move this logic to the Service (Well it would just move the responsibility) var box = Hive.box('settingsbox'); @@ -66,7 +67,7 @@ Type objectSetting(ObjectSettingRef ref, KeyValueStoreKey key, Type fallba } @riverpod -List stringListSetting(StringListSettingRef ref, KeyValueStoreKey key, [List? fallback]) { +List stringListSetting(Ref ref, KeyValueStoreKey key, [List? fallback]) { // This is a nice way to listen to changes in the settings box. // However, we might want to move this logic to the Service (Well it would just move the responsibility) var box = Hive.box('settingsbox'); diff --git a/common/lib/service/ui/bottom_sheet_service_interface.dart b/common/lib/service/ui/bottom_sheet_service_interface.dart index d09c59c0b..81f8463da 100644 --- a/common/lib/service/ui/bottom_sheet_service_interface.dart +++ b/common/lib/service/ui/bottom_sheet_service_interface.dart @@ -4,6 +4,7 @@ */ import 'package:flutter/material.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'bottom_sheet_service_interface.g.dart'; @@ -13,7 +14,7 @@ mixin BottomSheetIdentifierMixin implements Enum { } @Riverpod(keepAlive: true) -BottomSheetService bottomSheetService(BottomSheetServiceRef ref) => throw UnimplementedError(); +BottomSheetService bottomSheetService(Ref ref) => throw UnimplementedError(); abstract interface class BottomSheetService { Map get availableSheets; diff --git a/common/lib/service/ui/dialog_service_interface.dart b/common/lib/service/ui/dialog_service_interface.dart index 02a0066e9..2d0553de4 100644 --- a/common/lib/service/ui/dialog_service_interface.dart +++ b/common/lib/service/ui/dialog_service_interface.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'dialog_service_interface.g.dart'; @@ -114,7 +115,7 @@ class DialogResponse { } @Riverpod(keepAlive: true) -DialogService dialogService(DialogServiceRef ref) => throw UnimplementedError(); +DialogService dialogService(Ref ref) => throw UnimplementedError(); abstract interface class DialogService { bool get isDialogOpen; diff --git a/common/lib/service/ui/snackbar_service_interface.dart b/common/lib/service/ui/snackbar_service_interface.dart index 7f06f38d2..5fcfba1e3 100644 --- a/common/lib/service/ui/snackbar_service_interface.dart +++ b/common/lib/service/ui/snackbar_service_interface.dart @@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'dialog_service_interface.dart'; @@ -59,7 +60,7 @@ class SnackBarConfig { } @Riverpod(keepAlive: true) -SnackBarService snackBarService(SnackBarServiceRef ref) => throw UnimplementedError(); +SnackBarService snackBarService(Ref ref) => throw UnimplementedError(); abstract interface class SnackBarService { show(SnackBarConfig config); diff --git a/common/lib/service/ui/theme_service.dart b/common/lib/service/ui/theme_service.dart index 72b51f5fd..9e44b99e9 100644 --- a/common/lib/service/ui/theme_service.dart +++ b/common/lib/service/ui/theme_service.dart @@ -9,6 +9,7 @@ import 'package:common/service/selected_machine_service.dart'; import 'package:common/service/setting_service.dart'; import 'package:common/util/logger.dart'; import 'package:flutter/material.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:rxdart/rxdart.dart'; @@ -18,25 +19,25 @@ import '../payment_service.dart'; part 'theme_service.g.dart'; @Riverpod(keepAlive: true) -List themePack(ThemePackRef ref) { +List themePack(Ref ref) { throw UnimplementedError(); } @Riverpod() -ThemeService themeService(ThemeServiceRef ref) => ThemeService(ref); +ThemeService themeService(Ref ref) => ThemeService(ref); @riverpod -Stream activeTheme(ActiveThemeRef ref) => ref.watch(themeServiceProvider).themesStream; +Stream activeTheme(Ref ref) => ref.watch(themeServiceProvider).themesStream; class ThemeService { - ThemeService(ThemeServiceRef ref) + ThemeService(Ref ref) : themePacks = ref.watch(themePackProvider), _settingService = ref.watch(settingServiceProvider) { assert(themePacks.isNotEmpty, 'No ThemePacks provided!'); _init(ref); } - _init(ThemeServiceRef ref) { + _init(Ref ref) { ref.keepAlive(); selectSystemThemePack(); // Listen to changes in the selected machine and update the active theme accordingly diff --git a/common/lib/ui/components/nav/nav_widget_controller.dart b/common/lib/ui/components/nav/nav_widget_controller.dart index 36f19c66b..00a3c38ee 100644 --- a/common/lib/ui/components/nav/nav_widget_controller.dart +++ b/common/lib/ui/components/nav/nav_widget_controller.dart @@ -22,7 +22,7 @@ part 'nav_widget_controller.g.dart'; @riverpod class NavWidgetController extends _$NavWidgetController { - GoRouter get goRouter => ref.read(goRouterProvider); + GoRouter get _goRouter => ref.read(goRouterProvider); @override NavWidgetModel build() { @@ -103,23 +103,23 @@ class NavWidgetController extends _$NavWidgetController { } navigateTo(String route, {dynamic arguments}) { - if (goRouter.canPop()) goRouter.pop(); + if (_goRouter.canPop()) _goRouter.pop(); logger.i('Navigating to $route'); - goRouter.go(route, extra: arguments); + _goRouter.go(route, extra: arguments); } pushingTo(String route, {dynamic arguments}) async { - if (goRouter.canPop()) goRouter.pop(); + if (_goRouter.canPop()) _goRouter.pop(); logger.i('Pushing route to $route'); - await goRouter.push(route, extra: arguments); + await _goRouter.push(route, extra: arguments); } replace(String route, {dynamic arguments}) async { - // if (goRouter.canPop()) goRouter.pop(); + // if (_goRouter.canPop()) _goRouter.pop(); logger.i('Replacing route to $route'); - goRouter.replace(route, extra: arguments); + _goRouter.replace(route, extra: arguments); } void disable() { diff --git a/common/lib/ui/components/spool_widget.dart b/common/lib/ui/components/spool_widget.dart index b3679b5cc..71ec2c7ca 100644 --- a/common/lib/ui/components/spool_widget.dart +++ b/common/lib/ui/components/spool_widget.dart @@ -13,12 +13,12 @@ part 'spool_widget.g.dart'; // Used to ensure we only load the SVG once @Riverpod(keepAlive: true) -Future _svg(_SvgRef ref) async { +Future _svg(Ref ref) async { return rootBundle.loadString('assets/vector/spool-yellow-small.svg'); } @Riverpod(keepAlive: true) -Future _coloredSpool(_ColoredSpoolRef ref, String color, Brightness brightness) async { +Future _coloredSpool(Ref ref, String color, Brightness brightness) async { var rawSvg = await ref.watch(_svgProvider.future); // Extract alpha channel from color diff --git a/common/lib/util/extensions/ref_extension.dart b/common/lib/util/extensions/ref_extension.dart index f8e980a5f..5625ec01c 100644 --- a/common/lib/util/extensions/ref_extension.dart +++ b/common/lib/util/extensions/ref_extension.dart @@ -9,7 +9,7 @@ import 'package:common/util/logger.dart'; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -extension MobilerakerAutoDispose on AutoDisposeRef { +extension MobilerakerAutoDispose on Ref { // Returns a stream that alwways issues the latest/cached value of the provider // if the provider has one, even if multiple listeners listen to the stream! Stream watchAsSubject(ProviderListenable> provider, diff --git a/lib/ui/screens/dashboard/components/control_xyz_card.dart b/lib/ui/screens/dashboard/components/control_xyz_card.dart index 004ca44d9..4e412d0b5 100644 --- a/lib/ui/screens/dashboard/components/control_xyz_card.dart +++ b/lib/ui/screens/dashboard/components/control_xyz_card.dart @@ -9,7 +9,6 @@ import 'dart:math'; import 'package:common/data/dto/config/config_file.dart'; import 'package:common/data/dto/machine/print_state_enum.dart'; import 'package:common/data/dto/machine/printer_axis_enum.dart'; -import 'package:common/data/dto/machine/printer_builder.dart'; import 'package:common/service/machine_service.dart'; import 'package:common/service/moonraker/klippy_service.dart'; import 'package:common/service/moonraker/printer_service.dart'; @@ -92,7 +91,7 @@ class _Preview extends HookWidget { return ProviderScope( overrides: [ _controlXYZCardControllerProvider(_machineUUID).overrideWith(_ControlXYZCardPreviewController.new), - printerProvider(_machineUUID).overrideWith((provider) => Stream.value(PrinterBuilder.preview().build())), + printerProvider(_machineUUID).overrideWith(PrinterPreviewNotifier.new), toolheadInfoProvider(_machineUUID).overrideWith( (provider) => Stream.value( const ToolheadInfo( diff --git a/lib/ui/screens/dashboard/components/temperature_card/heater_sensor_card.dart b/lib/ui/screens/dashboard/components/temperature_card/heater_sensor_card.dart index a05b0c22f..1c1256b01 100644 --- a/lib/ui/screens/dashboard/components/temperature_card/heater_sensor_card.dart +++ b/lib/ui/screens/dashboard/components/temperature_card/heater_sensor_card.dart @@ -12,7 +12,6 @@ import 'package:common/data/dto/machine/heaters/extruder.dart'; import 'package:common/data/dto/machine/heaters/generic_heater.dart'; import 'package:common/data/dto/machine/heaters/heater_bed.dart'; import 'package:common/data/dto/machine/heaters/heater_mixin.dart'; -import 'package:common/data/dto/machine/printer_builder.dart'; import 'package:common/data/dto/machine/sensor_mixin.dart'; import 'package:common/data/dto/machine/temperature_sensor.dart'; import 'package:common/data/dto/machine/z_thermal_adjust.dart'; @@ -106,8 +105,7 @@ class _HeaterSensorCardPreviewState extends State<_HeaterSensorCardPreview> { _controllerProvider(_HeaterSensorCardPreview._machineUUID).overrideWith(() { return _PreviewController(); }), - printerProvider(_HeaterSensorCardPreview._machineUUID) - .overrideWith((provider) => Stream.value(PrinterBuilder.preview().build())), + printerProvider(_HeaterSensorCardPreview._machineUUID).overrideWith(PrinterPreviewNotifier.new), ], child: const HeaterSensorCard(machineUUID: _HeaterSensorCardPreview._machineUUID), ); diff --git a/lib/ui/screens/dev/dev_page.dart b/lib/ui/screens/dev/dev_page.dart index 2fcc90690..2e89f26b6 100644 --- a/lib/ui/screens/dev/dev_page.dart +++ b/lib/ui/screens/dev/dev_page.dart @@ -49,7 +49,7 @@ class DevPage extends HookConsumerWidget { logger.i('REBUILIDNG DEV PAGE!'); var selMachine = ref.watch(selectedMachineProvider).value; - var systemInfo = ref.watch(klipperSystemInfoProvider(selMachine!.uuid)); + var systemInfo = ref.watch(klippySystemInfoProvider(selMachine!.uuid)); Widget body = ListView( children: [ diff --git a/lib/ui/screens/printers/edit/printers_edit_controller.dart b/lib/ui/screens/printers/edit/printers_edit_controller.dart index 283fc5df5..ca8b40e07 100644 --- a/lib/ui/screens/printers/edit/printers_edit_controller.dart +++ b/lib/ui/screens/printers/edit/printers_edit_controller.dart @@ -67,7 +67,7 @@ Future machineRemoteSettings(MachineRemoteSettingsRef ref) { return ref.watch(machineSettingsProvider(machine.uuid).future); } -@riverpod +@Riverpod(dependencies: [currentlyEditing, machineRemoteSettings]) class PrinterEditController extends _$PrinterEditController { MachineService get _machineService => ref.read(machineServiceProvider); @@ -529,7 +529,7 @@ class PrinterEditController extends _$PrinterEditController { } } -@Riverpod(dependencies: [currentlyEditing, jrpcClientState]) +@Riverpod(dependencies: [currentlyEditing, currentlyEditing]) class WebcamListController extends _$WebcamListController { Machine get _machine => ref.read(currentlyEditingProvider); final List _camsToDelete = []; diff --git a/lib/ui/screens/spoolman/filament_form_page.dart b/lib/ui/screens/spoolman/filament_form_page.dart index f83f1ef51..141450a81 100644 --- a/lib/ui/screens/spoolman/filament_form_page.dart +++ b/lib/ui/screens/spoolman/filament_form_page.dart @@ -56,13 +56,13 @@ enum _FilamentFormFormComponent { } @Riverpod(dependencies: []) -GetVendor? _initialVendor(_) => throw UnimplementedError(); +GetVendor? _initialVendor(Ref _) => throw UnimplementedError(); @Riverpod(dependencies: []) -GetFilament? _initialFilament(_) => throw UnimplementedError(); +GetFilament? _initialFilament(Ref _) => throw UnimplementedError(); @Riverpod(dependencies: []) -_FormMode _formMode(_) => _FormMode.create; +_FormMode _formMode(Ref _) => _FormMode.create; class FilamentFormPage extends StatelessWidget { const FilamentFormPage({ diff --git a/lib/ui/screens/spoolman/spool_detail_page.dart b/lib/ui/screens/spoolman/spool_detail_page.dart index 73dd9c15d..4fad97144 100644 --- a/lib/ui/screens/spoolman/spool_detail_page.dart +++ b/lib/ui/screens/spoolman/spool_detail_page.dart @@ -44,7 +44,7 @@ part 'spool_detail_page.freezed.dart'; part 'spool_detail_page.g.dart'; @Riverpod(dependencies: []) -GetSpool _spool(_SpoolRef ref) { +GetSpool _spool(Ref ref) { throw UnimplementedError(); } diff --git a/lib/ui/screens/spoolman/vendor_form_page.dart b/lib/ui/screens/spoolman/vendor_form_page.dart index 1369efddb..e12e93183 100644 --- a/lib/ui/screens/spoolman/vendor_form_page.dart +++ b/lib/ui/screens/spoolman/vendor_form_page.dart @@ -38,12 +38,12 @@ enum _VendorFormFormComponent { } @Riverpod(dependencies: []) -GetVendor? _vendor(_VendorRef ref) { +GetVendor? _vendor(Ref ref) { throw UnimplementedError(); } @Riverpod(dependencies: []) -_FormMode _formMode(_FormModeRef ref) { +_FormMode _formMode(Ref ref) { return _FormMode.create; } From a8a8c69da1d240e42973ba4b9a6fe07170a03bcc Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 17 Nov 2024 20:45:49 +0100 Subject: [PATCH 20/64] fix: PaymentService missing changes --- common/lib/service/payment_service.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/common/lib/service/payment_service.dart b/common/lib/service/payment_service.dart index 76b90f389..e394791e8 100644 --- a/common/lib/service/payment_service.dart +++ b/common/lib/service/payment_service.dart @@ -12,6 +12,7 @@ import 'package:common/util/logger.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:purchases_flutter/purchases_flutter.dart'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:stringr/stringr.dart'; @@ -27,10 +28,12 @@ part 'payment_service.g.dart'; final customerInfoProvider = customerInfoNotifierProvider; @Riverpod(keepAlive: true) -Future customerInfo(CustomerInfoRef ref) async { - try { - var customerInfo = await Purchases.getCustomerInfo(); - logger.i('Got customerInfo: $customerInfo'); +class CustomerInfoNotifier extends _$CustomerInfoNotifier { + @override + Future build() async { + try { + var customerInfo = await Purchases.getCustomerInfo(); + logger.i('Got customerInfo: $customerInfo'); checkForExpired() async { logger.i('Checking for expired subs!'); @@ -64,13 +67,11 @@ Future customerInfo(CustomerInfoRef ref) async { @Riverpod(keepAlive: true) bool isSupporter(Ref ref) { - return true; return ref.watch(isSupporterAsyncProvider).valueOrNull == true; } @Riverpod(keepAlive: true) FutureOr isSupporterAsync(Ref ref) async { - return true; var customerInfo = await ref.watch(customerInfoProvider.future); return customerInfo.entitlements.active.containsKey('Supporter') == true; } From 8692292d40c1a3f77a12cca10b59a01457f32a33 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 17 Nov 2024 21:06:29 +0100 Subject: [PATCH 21/64] fix: More riverpod fixes --- lib/app_setup.dart | 146 +++++++++--------- lib/main.dart | 4 +- lib/routing/app_router.dart | 3 +- lib/service/ui/bottom_sheet_service_impl.dart | 2 +- lib/service/ui/dialog_service_impl.dart | 4 +- lib/service/ui/file_interaction_service.dart | 3 +- lib/service/ui/snackbar_service_impl.dart | 2 +- lib/ui/components/dashboard_card.dart | 6 +- .../components/filament_sensor_watcher.dart | 2 +- lib/ui/components/mjpeg/mjpeg_manager.dart | 3 +- .../toolhead_info_table_controller.dart | 3 +- .../config_file_details_controller.dart | 2 +- .../details/gcode_file_details_page.dart | 2 +- .../screens/fullcam/full_cam_controller.dart | 5 +- .../edit/components/macro_group_list.dart | 2 +- .../edit/printers_edit_controller.dart | 4 +- .../screens/setting/setting_controller.dart | 2 +- .../spoolman/filament_detail_page.dart | 2 +- lib/ui/screens/spoolman/spool_form_page.dart | 2 +- .../screens/spoolman/vendor_detail_page.dart | 2 +- lib/ui/theme/theme_setup.dart | 3 +- 21 files changed, 105 insertions(+), 99 deletions(-) diff --git a/lib/app_setup.dart b/lib/app_setup.dart index 49e31ef8a..dea467c53 100644 --- a/lib/app_setup.dart +++ b/lib/app_setup.dart @@ -254,81 +254,83 @@ initializeAvailableMachines(Ref ref) async { } @riverpod -Stream warmupProvider(WarmupProviderRef ref) async* { - logger.i('*****************************'); - logger.i('Mobileraker is warming up...'); - - logger.i('Mobileraker Version: ${await ref.read(versionInfoProvider.future)}'); - logger.i('*****************************'); - - // Firebase stuff - yield StartUpStep.firebaseCore; - await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); - - // only start listening after Firebase is initialized - ref.listenSelf((previous, next) { - if (next.hasError) { - var error = next.asError!; - FirebaseCrashlytics.instance.recordError( - error.error, - error.stackTrace, - fatal: true, - reason: 'Error during WarmUp!', - ); - } - }); - - yield StartUpStep.firebaseAppCheck; - await FirebaseAppCheck.instance.activate(); - - yield StartUpStep.firebaseRemoteConfig; - await ref.read(remoteConfigInstanceProvider).initialize(); - if (kDebugMode) { - FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(false); - } - - FlutterError.onError = (FlutterErrorDetails details) { - if (!kDebugMode) - logger.e('FlutterError caught by FlutterError.onError (${details.library})', details.exception, details.stack); - FirebaseCrashlytics.instance.recordFlutterError(details).ignore(); - }; - PlatformDispatcher.instance.onError = (error, stack) { - FirebaseCrashlytics.instance.recordError(error, stack).ignore(); - return true; - }; - yield StartUpStep.firebaseAnalytics; - ref.read(analyticsProvider).logAppOpen().ignore(); - - yield StartUpStep.firebaseAuthUi; - // Just make sure it is created! - ref.read(firebaseUserProvider); - - setupLicenseRegistry(); - - // Prepare "Database" - yield StartUpStep.hiveBoxes; - await setupBoxes(); - - // Prepare Translations - yield StartUpStep.easyLocalization; - await EasyLocalization.ensureInitialized(); - - yield StartUpStep.paymentService; - await ref.read(paymentServiceProvider).initialize(); - - // await for the initial rout provider to be ready and setup! - yield StartUpStep.goRouter; - await ref.read(initialRouteProvider.future); - logger.i('Completed initialRoute init'); - // Wait for the machines to be ready - yield StartUpStep.initMachines; - await initializeAvailableMachines(ref); +class Warmup extends _$Warmup { + @override + Stream build() async* { + logger.i('*****************************'); + logger.i('Mobileraker is warming up...'); + + logger.i('Mobileraker Version: ${await ref.read(versionInfoProvider.future)}'); + logger.i('*****************************'); + + // Firebase stuff + yield StartUpStep.firebaseCore; + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + + // only start listening after Firebase is initialized + listenSelf((previous, next) { + if (next.hasError) { + var error = next.asError!; + FirebaseCrashlytics.instance.recordError( + error.error, + error.stackTrace, + fatal: true, + reason: 'Error during WarmUp!', + ); + } + }); - yield StartUpStep.notificationService; - await ref.read(notificationServiceProvider).initialize([AWESOME_FCM_LICENSE_ANDROID, AWESOME_FCM_LICENSE_IOS]); + yield StartUpStep.firebaseAppCheck; + await FirebaseAppCheck.instance.activate(); + yield StartUpStep.firebaseRemoteConfig; + await ref.read(remoteConfigInstanceProvider).initialize(); + if (kDebugMode) { + FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(false); + } - yield StartUpStep.complete; + FlutterError.onError = (FlutterErrorDetails details) { + if (!kDebugMode) + logger.e('FlutterError caught by FlutterError.onError (${details.library})', details.exception, details.stack); + FirebaseCrashlytics.instance.recordFlutterError(details).ignore(); + }; + PlatformDispatcher.instance.onError = (error, stack) { + FirebaseCrashlytics.instance.recordError(error, stack).ignore(); + return true; + }; + yield StartUpStep.firebaseAnalytics; + ref.read(analyticsProvider).logAppOpen().ignore(); + + yield StartUpStep.firebaseAuthUi; + // Just make sure it is created! + ref.read(firebaseUserProvider); + + setupLicenseRegistry(); + + // Prepare "Database" + yield StartUpStep.hiveBoxes; + await setupBoxes(); + + // Prepare Translations + yield StartUpStep.easyLocalization; + await EasyLocalization.ensureInitialized(); + + yield StartUpStep.paymentService; + await ref.read(paymentServiceProvider).initialize(); + + // await for the initial rout provider to be ready and setup! + yield StartUpStep.goRouter; + await ref.read(initialRouteProvider.future); + logger.i('Completed initialRoute init'); + // Wait for the machines to be ready + yield StartUpStep.initMachines; + await initializeAvailableMachines(ref); + + yield StartUpStep.notificationService; + await ref.read(notificationServiceProvider).initialize([AWESOME_FCM_LICENSE_ANDROID, AWESOME_FCM_LICENSE_IOS]); + + yield StartUpStep.complete; + } } enum StartUpStep { diff --git a/lib/main.dart b/lib/main.dart index 8f1da77b2..5dd0725b4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -150,7 +150,7 @@ class _WarmUp extends HookConsumerWidget { return Container( color: splashBgColorForBrightness(brightness), - child: ref.watch(warmupProviderProvider).when( + child: ref.watch(warmupProvider).when( data: (step) { if (step == StartUpStep.complete) { return ResponsiveBuilder(childBuilder: (context) => const MyApp()); @@ -295,7 +295,7 @@ class _EmojiIndicator extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - var step = ref.watch(warmupProviderProvider).valueOrNull; + var step = ref.watch(warmupProvider).valueOrNull; if (step == null) return const SizedBox.shrink(); return Text(step.emoji); } diff --git a/lib/routing/app_router.dart b/lib/routing/app_router.dart index cc7061f49..e7e62362c 100644 --- a/lib/routing/app_router.dart +++ b/lib/routing/app_router.dart @@ -19,6 +19,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_icons/flutter_icons.dart'; import 'package:go_router/go_router.dart'; import 'package:go_transitions/go_transitions.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mobileraker/service/ui/bottom_sheet_service_impl.dart'; import 'package:mobileraker/ui/components/app_version_text.dart'; import 'package:mobileraker/ui/screens/console/console_page.dart'; @@ -88,7 +89,7 @@ enum AppRoute implements RouteDefinitionMixin { } @riverpod -Future initialRoute(InitialRouteRef ref) async { +Future initialRoute(Ref ref) async { ref.keepAlive(); SettingService settingService = ref.watch(settingServiceProvider); diff --git a/lib/service/ui/bottom_sheet_service_impl.dart b/lib/service/ui/bottom_sheet_service_impl.dart index cf6c5bb8c..3fdb90680 100644 --- a/lib/service/ui/bottom_sheet_service_impl.dart +++ b/lib/service/ui/bottom_sheet_service_impl.dart @@ -45,7 +45,7 @@ enum SheetType implements BottomSheetIdentifierMixin { confirm; } -BottomSheetService bottomSheetServiceImpl(BottomSheetServiceRef ref) => BottomSheetServiceImpl(ref); +BottomSheetService bottomSheetServiceImpl(Ref ref) => BottomSheetServiceImpl(ref); class BottomSheetServiceImpl implements BottomSheetService { BottomSheetServiceImpl(this.ref); diff --git a/lib/service/ui/dialog_service_impl.dart b/lib/service/ui/dialog_service_impl.dart index c796b99d2..81360a0a4 100644 --- a/lib/service/ui/dialog_service_impl.dart +++ b/lib/service/ui/dialog_service_impl.dart @@ -65,12 +65,12 @@ enum DialogType implements DialogIdentifierMixin { filamentOperation, } -DialogService dialogServiceImpl(DialogServiceRef ref) => DialogServiceImpl(ref); +DialogService dialogServiceImpl(Ref ref) => DialogServiceImpl(ref); class DialogServiceImpl implements DialogService { DialogServiceImpl(this._ref); - final DialogServiceRef _ref; + final Ref _ref; DialogRequest? _currentDialogRequest; diff --git a/lib/service/ui/file_interaction_service.dart b/lib/service/ui/file_interaction_service.dart index 65c5eeb9d..a849b3548 100644 --- a/lib/service/ui/file_interaction_service.dart +++ b/lib/service/ui/file_interaction_service.dart @@ -32,6 +32,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mobileraker_pro/job_queue/service/job_queue_service.dart'; import 'package:mobileraker_pro/service/ui/pro_routes.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -49,7 +50,7 @@ part 'file_interaction_service.g.dart'; final _zipDateFormat = DateFormat('yyyy-MM-dd_HH-mm-ss'); @riverpod -FileInteractionService fileInteractionService(FileInteractionServiceRef ref, String machineUUID) { +FileInteractionService fileInteractionService(Ref ref, String machineUUID) { return FileInteractionService( machineUUID, ref.watch(bottomSheetServiceProvider), diff --git a/lib/service/ui/snackbar_service_impl.dart b/lib/service/ui/snackbar_service_impl.dart index 6ccdad9c3..ec5571cc9 100644 --- a/lib/service/ui/snackbar_service_impl.dart +++ b/lib/service/ui/snackbar_service_impl.dart @@ -9,7 +9,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mobileraker/ui/components/snackbar/snackbar.dart'; -SnackBarService snackBarServiceImpl(SnackBarServiceRef ref) => SnackBarServiceImpl(ref); +SnackBarService snackBarServiceImpl(Ref ref) => SnackBarServiceImpl(ref); class SnackBarServiceImpl implements SnackBarService { const SnackBarServiceImpl(this.ref); diff --git a/lib/ui/components/dashboard_card.dart b/lib/ui/components/dashboard_card.dart index 1120adcd2..de4acc784 100644 --- a/lib/ui/components/dashboard_card.dart +++ b/lib/ui/components/dashboard_card.dart @@ -34,17 +34,17 @@ import '../screens/dashboard/components/z_offset_card.dart'; part 'dashboard_card.g.dart'; @Riverpod(dependencies: [], keepAlive: true) -DashboardComponentType _cardType(_CardTypeRef ref) { +DashboardComponentType _cardType(Ref ref) { throw UnimplementedError(); } @Riverpod(dependencies: [], keepAlive: true) -String _cardUUID(_CardUUIDRef ref) { +String _cardUUID(Ref ref) { throw UnimplementedError(); } @Riverpod(dependencies: [_cardUUID, _cardType]) -String dashboardCardUUID(DashboardCardUUIDRef ref, String machineUUID) { +String dashboardCardUUID(Ref ref, String machineUUID) { final dashboard = ref.watch(dashboardLayoutProvider(machineUUID).requireValue()); if (dashboard.created == null) { diff --git a/lib/ui/components/filament_sensor_watcher.dart b/lib/ui/components/filament_sensor_watcher.dart index 853f3e859..7441abeb7 100644 --- a/lib/ui/components/filament_sensor_watcher.dart +++ b/lib/ui/components/filament_sensor_watcher.dart @@ -115,7 +115,7 @@ class _FilamentSensorWatcherState extends ConsumerState { // Provider to keep track of triggered filament sensors during the lifetime of the app rather than just the widget @riverpod -Map<(ConfigFileObjectIdentifiers, String), bool> _triggered(_TriggeredRef ref, String machineUUID) { +Map<(ConfigFileObjectIdentifiers, String), bool> _triggered(Ref ref, String machineUUID) { ref.keepAlive(); return {}; } diff --git a/lib/ui/components/mjpeg/mjpeg_manager.dart b/lib/ui/components/mjpeg/mjpeg_manager.dart index 2f85c529e..7dd2ef082 100644 --- a/lib/ui/components/mjpeg/mjpeg_manager.dart +++ b/lib/ui/components/mjpeg/mjpeg_manager.dart @@ -5,6 +5,7 @@ import 'package:dio/dio.dart'; import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mobileraker/ui/components/mjpeg/stream_mjpeg_manager.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -25,7 +26,7 @@ abstract class MjpegManager { } @riverpod -MjpegManager mjpegManager(MjpegManagerRef ref, Dio dio, MjpegConfig config) { +MjpegManager mjpegManager(Ref ref, Dio dio, MjpegConfig config) { var manager = switch (config.mode) { MjpegMode.adaptiveStream => AdaptiveMjpegManager(dio, config), MjpegMode.stream => StreamMjpegManager(dio, config), diff --git a/lib/ui/screens/dashboard/components/toolhead_info/toolhead_info_table_controller.dart b/lib/ui/screens/dashboard/components/toolhead_info/toolhead_info_table_controller.dart index 57a4aec0e..1747951bb 100644 --- a/lib/ui/screens/dashboard/components/toolhead_info/toolhead_info_table_controller.dart +++ b/lib/ui/screens/dashboard/components/toolhead_info/toolhead_info_table_controller.dart @@ -13,6 +13,7 @@ import 'package:common/util/extensions/double_extension.dart'; import 'package:common/util/extensions/ref_extension.dart'; import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'toolhead_info_table_controller.freezed.dart'; @@ -118,7 +119,7 @@ class ToolheadInfo with _$ToolheadInfo { } @riverpod -Stream toolheadInfo(ToolheadInfoRef ref, String machineUUID) async* { +Stream toolheadInfo(Ref ref, String machineUUID) async* { ref.keepAliveFor(); final applyOffsetSettings = ref.watch(boolSettingProvider(AppSettingKeys.applyOffsetsToPostion)); diff --git a/lib/ui/screens/files/details/config_file_details_controller.dart b/lib/ui/screens/files/details/config_file_details_controller.dart index 053d84ecc..ddec3c9bd 100644 --- a/lib/ui/screens/files/details/config_file_details_controller.dart +++ b/lib/ui/screens/files/details/config_file_details_controller.dart @@ -23,7 +23,7 @@ part 'config_file_details_controller.freezed.dart'; part 'config_file_details_controller.g.dart'; @Riverpod(dependencies: []) -GenericFile configFile(ConfigFileRef ref) => throw UnimplementedError(); +GenericFile configFile(Ref ref) => throw UnimplementedError(); final configFileDetailsControllerProvider = StateNotifierProvider.autoDispose( diff --git a/lib/ui/screens/files/details/gcode_file_details_page.dart b/lib/ui/screens/files/details/gcode_file_details_page.dart index 086a18702..df896b5c9 100644 --- a/lib/ui/screens/files/details/gcode_file_details_page.dart +++ b/lib/ui/screens/files/details/gcode_file_details_page.dart @@ -564,7 +564,7 @@ class _PropertyTile extends StatelessWidget { } @Riverpod(dependencies: []) -GCodeFile gcode(GcodeRef ref) => throw UnimplementedError(); +GCodeFile gcode(Ref ref) => throw UnimplementedError(); @Riverpod(dependencies: [gcode]) class _GCodeFileDetailsController extends _$GCodeFileDetailsController { diff --git a/lib/ui/screens/fullcam/full_cam_controller.dart b/lib/ui/screens/fullcam/full_cam_controller.dart index b29ef6c13..4b5fc2e61 100644 --- a/lib/ui/screens/fullcam/full_cam_controller.dart +++ b/lib/ui/screens/fullcam/full_cam_controller.dart @@ -7,15 +7,16 @@ import 'package:common/data/model/hive/machine.dart'; import 'package:common/data/model/moonraker_db/webcam_info.dart'; import 'package:common/service/setting_service.dart'; import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'full_cam_controller.g.dart'; @Riverpod(dependencies: []) -Machine fullCamMachine(FullCamMachineRef ref) => throw UnimplementedError(); +Machine fullCamMachine(Ref ref) => throw UnimplementedError(); @Riverpod(dependencies: []) -WebcamInfo initialCam(InitialCamRef ref) => throw UnimplementedError(); +WebcamInfo initialCam(Ref ref) => throw UnimplementedError(); @Riverpod(dependencies: [fullCamMachine, initialCam, settingService]) class FullCamPageController extends _$FullCamPageController { diff --git a/lib/ui/screens/printers/edit/components/macro_group_list.dart b/lib/ui/screens/printers/edit/components/macro_group_list.dart index 61f03676a..e4f3a4e60 100644 --- a/lib/ui/screens/printers/edit/components/macro_group_list.dart +++ b/lib/ui/screens/printers/edit/components/macro_group_list.dart @@ -447,6 +447,6 @@ class MacroGroupListController extends _$MacroGroupListController { } @riverpod -ExpansionTileController _expansionTileController(_ExpansionTileControllerRef ref, String macroGroupUUID) { +ExpansionTileController _expansionTileController(Ref ref, String macroGroupUUID) { return ExpansionTileController(); } diff --git a/lib/ui/screens/printers/edit/printers_edit_controller.dart b/lib/ui/screens/printers/edit/printers_edit_controller.dart index ca8b40e07..4df43a994 100644 --- a/lib/ui/screens/printers/edit/printers_edit_controller.dart +++ b/lib/ui/screens/printers/edit/printers_edit_controller.dart @@ -59,10 +59,10 @@ part 'printers_edit_controller.g.dart'; GlobalKey editPrinterFormKey(EditPrinterFormKeyRef _) => GlobalKey(); @Riverpod(dependencies: []) -Machine currentlyEditing(CurrentlyEditingRef ref) => throw UnimplementedError(); +Machine currentlyEditing(Ref ref) => throw UnimplementedError(); @Riverpod(dependencies: [currentlyEditing]) -Future machineRemoteSettings(MachineRemoteSettingsRef ref) { +Future machineRemoteSettings(Ref ref) { var machine = ref.watch(currentlyEditingProvider); return ref.watch(machineSettingsProvider(machine.uuid).future); } diff --git a/lib/ui/screens/setting/setting_controller.dart b/lib/ui/screens/setting/setting_controller.dart index 3ee81c1a2..0b0a28aa2 100644 --- a/lib/ui/screens/setting/setting_controller.dart +++ b/lib/ui/screens/setting/setting_controller.dart @@ -36,7 +36,7 @@ final notificationPermissionControllerProvider = ); class NotificationPermissionController extends StateNotifier { - NotificationPermissionController(AutoDisposeRef ref) + NotificationPermissionController(ref) : notificationService = ref.watch(notificationServiceProvider), super(true) { evaluatePermission(); diff --git a/lib/ui/screens/spoolman/filament_detail_page.dart b/lib/ui/screens/spoolman/filament_detail_page.dart index 600ee2e19..6d22592b6 100644 --- a/lib/ui/screens/spoolman/filament_detail_page.dart +++ b/lib/ui/screens/spoolman/filament_detail_page.dart @@ -32,7 +32,7 @@ import 'common_detail.dart'; part 'filament_detail_page.g.dart'; @Riverpod(dependencies: []) -GetFilament _filament(_FilamentRef ref) { +GetFilament _filament(Ref ref) { throw UnimplementedError(); } diff --git a/lib/ui/screens/spoolman/spool_form_page.dart b/lib/ui/screens/spoolman/spool_form_page.dart index 9d8a89575..0dc71e760 100644 --- a/lib/ui/screens/spoolman/spool_form_page.dart +++ b/lib/ui/screens/spoolman/spool_form_page.dart @@ -62,7 +62,7 @@ GetFilament? _initialFilament(_) => throw UnimplementedError(); GetSpool? _initialSpool(_) => throw UnimplementedError(); @Riverpod(dependencies: []) -_FormMode _formMode(_FormModeRef ref) => _FormMode.create; +_FormMode _formMode(Ref ref) => _FormMode.create; class SpoolFormPage extends StatelessWidget { const SpoolFormPage({ diff --git a/lib/ui/screens/spoolman/vendor_detail_page.dart b/lib/ui/screens/spoolman/vendor_detail_page.dart index 50275e163..b3b579aa0 100644 --- a/lib/ui/screens/spoolman/vendor_detail_page.dart +++ b/lib/ui/screens/spoolman/vendor_detail_page.dart @@ -31,7 +31,7 @@ import 'common_detail.dart'; part 'vendor_detail_page.g.dart'; @Riverpod(dependencies: []) -GetVendor _vendor(_VendorRef ref) { +GetVendor _vendor(Ref ref) { throw UnimplementedError(); } diff --git a/lib/ui/theme/theme_setup.dart b/lib/ui/theme/theme_setup.dart index 3edb93189..5961d6bbb 100644 --- a/lib/ui/theme/theme_setup.dart +++ b/lib/ui/theme/theme_setup.dart @@ -9,7 +9,6 @@ import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; const int darkRed = 0xffb21818; var redish = const MaterialColor(darkRed, { @@ -645,7 +644,7 @@ ThemePack _oePack() { ); } -List themePacks(ProviderRef ref) { +List themePacks(Ref ref) { var isSupporter = ref.watch(isSupporterAsyncProvider).valueOrNull; return [ _mobilerakerPack(), From 6c962a39032a1495caec3a3bf383bc4afbae59a2 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 17 Nov 2024 21:13:03 +0100 Subject: [PATCH 22/64] refactor: Cleanup --- .../dialog/exclude_object/exclude_object_dialog.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ui/components/dialog/exclude_object/exclude_object_dialog.dart b/lib/ui/components/dialog/exclude_object/exclude_object_dialog.dart index 3cc7ef86e..8af20411f 100644 --- a/lib/ui/components/dialog/exclude_object/exclude_object_dialog.dart +++ b/lib/ui/components/dialog/exclude_object/exclude_object_dialog.dart @@ -119,7 +119,7 @@ class _ExcludeObjectDialog extends ConsumerWidget { labelText: 'dialogs.exclude_object.label'.tr(), ), ), - (isConfirmed) ? const ExcludeBtnRow() : const DefaultBtnRow(), + (isConfirmed) ? const _ExcludeBtnRow() : const _DefaultBtnRow(), ], ); }, @@ -131,8 +131,8 @@ class _ExcludeObjectDialog extends ConsumerWidget { } } -class DefaultBtnRow extends ConsumerWidget { - const DefaultBtnRow({super.key}); +class _DefaultBtnRow extends ConsumerWidget { + const _DefaultBtnRow({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -158,8 +158,8 @@ class DefaultBtnRow extends ConsumerWidget { } } -class ExcludeBtnRow extends ConsumerWidget { - const ExcludeBtnRow({super.key}); +class _ExcludeBtnRow extends ConsumerWidget { + const _ExcludeBtnRow({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { From 57a30aff06ef6c8a003ec96030115dc5ecb84ff2 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 17 Nov 2024 21:38:26 +0100 Subject: [PATCH 23/64] feat: Step selector now also works even if extruding is not possible --- lib/ui/screens/dashboard/components/control_extruder_card.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ui/screens/dashboard/components/control_extruder_card.dart b/lib/ui/screens/dashboard/components/control_extruder_card.dart index 68a4aaa61..648bc4a7c 100644 --- a/lib/ui/screens/dashboard/components/control_extruder_card.dart +++ b/lib/ui/screens/dashboard/components/control_extruder_card.dart @@ -345,7 +345,7 @@ class _CardBody extends ConsumerWidget { const SizedBox(height: 8), SingleValueSelector( selectedIndex: model.stepIndex, - onSelected: canExtrude ? controller.onSelectedStepChanged : null, + onSelected: controller.onSelectedStepChanged, values: [for (var step in model.steps) step.toString()], ), const SizedBox(height: 8), From 55f638c8b8041f547e946bf43c4b0d5a978657bd Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Wed, 20 Nov 2024 21:04:28 +0100 Subject: [PATCH 24/64] fix: Remote sheet not working correctly due to scoped dependencies --- .../add_remote_connection_bottom_sheet.dart | 2 +- .../edit/printers_edit_controller.dart | 31 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/ui/components/bottomsheet/remote_connection/add_remote_connection_bottom_sheet.dart b/lib/ui/components/bottomsheet/remote_connection/add_remote_connection_bottom_sheet.dart index c779824c0..7fc0bd650 100644 --- a/lib/ui/components/bottomsheet/remote_connection/add_remote_connection_bottom_sheet.dart +++ b/lib/ui/components/bottomsheet/remote_connection/add_remote_connection_bottom_sheet.dart @@ -353,7 +353,7 @@ class _ObicoTab extends ConsumerWidget { ), validator: FormBuilderValidators.compose([ // FormBuilderValidators.required(), - FormBuilderValidators.url(requireTld: false), + FormBuilderValidators.url(requireTld: false, checkNullOrEmpty: false), ]), ), ], diff --git a/lib/ui/screens/printers/edit/printers_edit_controller.dart b/lib/ui/screens/printers/edit/printers_edit_controller.dart index 4df43a994..da08d5eb0 100644 --- a/lib/ui/screens/printers/edit/printers_edit_controller.dart +++ b/lib/ui/screens/printers/edit/printers_edit_controller.dart @@ -67,7 +67,14 @@ Future machineRemoteSettings(Ref ref) { return ref.watch(machineSettingsProvider(machine.uuid).future); } -@Riverpod(dependencies: [currentlyEditing, machineRemoteSettings]) +@Riverpod(dependencies: [ + currentlyEditing, + machineRemoteSettings, + _ObicoTunnel, + _OctoEverywhere, + _RemoteInterface, + WebcamListController +]) class PrinterEditController extends _$PrinterEditController { MachineService get _machineService => ref.read(machineServiceProvider); @@ -275,14 +282,14 @@ class PrinterEditController extends _$PrinterEditController { } } - printerThemeSupporterDialog() { + void printerThemeSupporterDialog() { ref.read(dialogServiceProvider).show(DialogRequest( type: DialogType.supporterOnlyFeature, body: tr('components.supporter_only_feature.printer_theme'), )); } - resetFcmCache() async { + Future resetFcmCache() async { var dialogResponse = await ref.read(dialogServiceProvider).showDangerConfirm( title: tr( 'pages.printer_edit.confirm_fcm_reset.title', @@ -326,7 +333,7 @@ class PrinterEditController extends _$PrinterEditController { ref.invalidate(permissionStatusProvider(Permission.location)); } - deleteIt() async { + Future deleteIt() async { var dialogResponse = await ref.read(dialogServiceProvider).showDangerConfirm( title: tr( 'pages.printer_edit.confirm_deletion.title', @@ -346,17 +353,15 @@ class PrinterEditController extends _$PrinterEditController { } } - openImportSettings() { - ref - .read(dialogServiceProvider) - .show(DialogRequest( + Future openImportSettings() async { + final res = await ref.read(dialogServiceProvider).show(DialogRequest( type: DialogType.importSettings, data: ref.read(currentlyEditingProvider), - )) - .then(onImportSettingsReturns); + )); + onImportSettingsReturns(res); } - onImportSettingsReturns(DialogResponse? response) { + void onImportSettingsReturns(DialogResponse? response) { if (response != null && response.confirmed) { FormBuilderState formState = ref.read(editPrinterFormKeyProvider).currentState!; ImportSettingsDialogViewResults result = response.data; @@ -413,7 +418,7 @@ class PrinterEditController extends _$PrinterEditController { } } - openRemoteConnectionSheet() async { + Future openRemoteConnectionSheet() async { var octoEverywhere = ref.read(_octoEverywhereProvider); var remoteInterface = ref.read(_remoteInterfaceProvider); var obicoTunnel = ref.read(_obicoTunnelProvider); @@ -529,7 +534,7 @@ class PrinterEditController extends _$PrinterEditController { } } -@Riverpod(dependencies: [currentlyEditing, currentlyEditing]) +@Riverpod(dependencies: [currentlyEditing]) class WebcamListController extends _$WebcamListController { Machine get _machine => ref.read(currentlyEditingProvider); final List _camsToDelete = []; From 67d2a99c21288e9533538f335890146b204c92c9 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Wed, 20 Nov 2024 21:31:51 +0100 Subject: [PATCH 25/64] fix: Obico "One-Click" setup not working due to autodisposing --- common/lib/service/machine_service.dart | 2 +- common/lib/service/obico/obico_tunnel_service.dart | 2 +- lib/ui/screens/printers/add/printers_add_controller.dart | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/common/lib/service/machine_service.dart b/common/lib/service/machine_service.dart index e3c1b23f5..39283d8dc 100644 --- a/common/lib/service/machine_service.dart +++ b/common/lib/service/machine_service.dart @@ -741,7 +741,7 @@ class MachineService { logger.w('Rpc Client was not connected, could not fetch obico.printer_id. User can select by himself!'); } - return ref.watch(obicoTunnelServiceProvider(baseUrl)).linkApp(printerId: obicoPrinterId); + return ref.read(obicoTunnelServiceProvider(baseUrl)).linkApp(printerId: obicoPrinterId); } Future reordered(String machineUUID, int oldIndex, int newIndex) async { diff --git a/common/lib/service/obico/obico_tunnel_service.dart b/common/lib/service/obico/obico_tunnel_service.dart index e2fdee77d..32573e23c 100644 --- a/common/lib/service/obico/obico_tunnel_service.dart +++ b/common/lib/service/obico/obico_tunnel_service.dart @@ -31,7 +31,7 @@ ObicoTunnelService obicoTunnelService(Ref ref, [Uri? uri]) { class ObicoTunnelService { ObicoTunnelService(Ref ref, Uri uri) : _obicoUri = uri, - _dio = ref.read(obicoApiClientProvider(uri.toString())); + _dio = ref.watch(obicoApiClientProvider(uri.toString())); final Dio _dio; diff --git a/lib/ui/screens/printers/add/printers_add_controller.dart b/lib/ui/screens/printers/add/printers_add_controller.dart index be9bb3b8b..6ba0ec0c9 100644 --- a/lib/ui/screens/printers/add/printers_add_controller.dart +++ b/lib/ui/screens/printers/add/printers_add_controller.dart @@ -26,6 +26,7 @@ import 'package:common/service/ui/snackbar_service_interface.dart'; import 'package:common/ui/theme/theme_pack.dart'; import 'package:common/util/extensions/dio_options_extension.dart'; import 'package:common/util/extensions/object_extension.dart'; +import 'package:common/util/extensions/ref_extension.dart'; import 'package:common/util/extensions/uri_extension.dart'; import 'package:common/util/logger.dart'; import 'package:common/util/misc.dart'; @@ -91,7 +92,6 @@ class PrinterAddViewController extends _$PrinterAddViewController { try { AppPortalResult appPortalResult = await appConnectionService.linkAppWithOcto(); - AppConnectionInfoResponse appConnectionInfo = await appConnectionService.getInfo(appPortalResult.appApiToken); var infoResult = appConnectionInfo.result; @@ -130,7 +130,8 @@ class PrinterAddViewController extends _$PrinterAddViewController { addFromObico() async { if (state.nonSupporterError != null) return; state = state.copyWith(step: 3); - var tunnelService = ref.read(obicoTunnelServiceProvider()); + var keepAliveExternally = ref.keepAliveExternally(obicoTunnelServiceProvider()); + var tunnelService = keepAliveExternally.read(); try { var tunnel = await tunnelService.linkApp(); @@ -157,6 +158,8 @@ class PrinterAddViewController extends _$PrinterAddViewController { } catch (e, s) { logger.e('Error while trying to add printer via Obico', e, s); _thirdPartyAddError('Error', e.toString()); + } finally { + keepAliveExternally.close(); } } From be2adb79e21d7cdfe79d20cb3a7309395a26e032 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Wed, 20 Nov 2024 21:33:00 +0100 Subject: [PATCH 26/64] doc: Added obico fix to changelog --- docs/changelog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 9696fdf07..1b0e56c83 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -18,9 +18,10 @@ and `temperature_fan`, with the same name. The app is now aware of the object kind and will allow users to have objects of the same class with the same name but different kind, applies to fans, leds and filament sensors. - - **WebRtc Cam Creation**: Fixed an issue in the code that handles the creation of WebRtc cams in the app's internals. This caused to many webRTC cam streams to be opened in a short time, which could lead to a crash of the app. +- **Obico One Click Setup**: Fixed an issue where the app would not allow to setup an Obico connection with a single + click via the app page due to an issue with the used http client. ## [2.8.2] - 2024-10-31 From 70c776e18296a52d103defae3069f5704a53b673 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Wed, 20 Nov 2024 22:41:14 +0100 Subject: [PATCH 27/64] feat: Randomized order of 3rd party remote services for fairness --- .../add_remote_connection_bottom_sheet.dart | 63 +++++++++++++------ ...te_connection_bottom_sheet_controller.dart | 13 +--- .../printers/add/printers_add_page.dart | 55 ++++++++-------- 3 files changed, 75 insertions(+), 56 deletions(-) diff --git a/lib/ui/components/bottomsheet/remote_connection/add_remote_connection_bottom_sheet.dart b/lib/ui/components/bottomsheet/remote_connection/add_remote_connection_bottom_sheet.dart index 7fc0bd650..30d2c7e44 100644 --- a/lib/ui/components/bottomsheet/remote_connection/add_remote_connection_bottom_sheet.dart +++ b/lib/ui/components/bottomsheet/remote_connection/add_remote_connection_bottom_sheet.dart @@ -3,6 +3,8 @@ * All rights reserved. */ +import 'dart:math'; + import 'package:common/network/json_rpc_client.dart'; import 'package:common/service/firebase/remote_config.dart'; import 'package:common/ui/components/info_card.dart'; @@ -33,22 +35,31 @@ class AddRemoteConnectionBottomSheet extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return ProviderScope( overrides: [sheetArgsProvider.overrideWithValue(args)], - child: const _AddRemoteConnectionBottomSheet(), + child: _AddRemoteConnectionBottomSheet(thirdPartyFairness: Random().nextInt(2)), ); } } class _AddRemoteConnectionBottomSheet extends HookConsumerWidget { - const _AddRemoteConnectionBottomSheet({super.key}); + const _AddRemoteConnectionBottomSheet({super.key, required this.thirdPartyFairness}); + + // random offset for fairness to determine the order of the tabs + final int thirdPartyFairness; @override Widget build(BuildContext context, WidgetRef ref) { - var obicoEnabled = ref.watch(remoteConfigBoolProvider('obico_remote_connection')); + final obicoEnabled = ref.watch(remoteConfigBoolProvider('obico_remote_connection')); + final obicoIndex = 1 - thirdPartyFairness; + final octoIndex = obicoEnabled ? 0 + thirdPartyFairness : 0; - var activeIndex = ref.watch(addRemoteConnectionBottomSheetControllerProvider.select((value) { + final initialIndex = ref.watch(addRemoteConnectionBottomSheetControllerProvider.select((value) { if (obicoEnabled && value.obicoTunnel != null) { - return 1; + return obicoIndex; } + if (value.octoEverywhere != null) { + return octoIndex; + } + if (value.remoteInterface != null) { if (obicoEnabled) { return 2; @@ -60,30 +71,34 @@ class _AddRemoteConnectionBottomSheet extends HookConsumerWidget { var tabController = useTabController( initialLength: obicoEnabled ? 3 : 2, - initialIndex: activeIndex, + initialIndex: initialIndex, ); // Close keyboard when switching tabs - useEffect(() { - closeKeyBoard() { - FocusManager.instance.primaryFocus?.unfocus(); - } + useEffect( + () { + closeKeyBoard() { + FocusManager.instance.primaryFocus?.unfocus(); + } - tabController.addListener(closeKeyBoard); + tabController.addListener(closeKeyBoard); - return () => tabController.removeListener(closeKeyBoard); - }, [tabController]); + return () => tabController.removeListener(closeKeyBoard); + }, + [tabController], + ); return SheetContentScaffold( - appBar: _TabHeader(tabController: tabController, obicoEnabled: obicoEnabled), + appBar: _TabHeader(tabController: tabController, obicoEnabled: obicoEnabled, octoIndex: octoIndex), body: FormBuilder( key: ref.watch(formKeyProvider), autovalidateMode: AutovalidateMode.onUserInteraction, child: TabBarView( controller: tabController, children: [ - const _OctoTab(), + if (octoIndex == 0) const _OctoTab(), if (obicoEnabled) const _ObicoTab(), + if (octoIndex == 1) const _OctoTab(), const _ManualTab(), ], ), @@ -93,10 +108,11 @@ class _AddRemoteConnectionBottomSheet extends HookConsumerWidget { } class _TabHeader extends StatelessWidget implements PreferredSizeWidget { - const _TabHeader({super.key, required this.tabController, required this.obicoEnabled}); + const _TabHeader({super.key, required this.tabController, required this.obicoEnabled, required this.octoIndex}); final TabController tabController; final bool obicoEnabled; + final int octoIndex; @override Widget build(BuildContext context) { @@ -117,17 +133,24 @@ class _TabHeader extends StatelessWidget implements PreferredSizeWidget { automaticIndicatorColorAdjustment: false, tabAlignment: TabAlignment.start, tabs: [ - Tab( - text: tr( - 'bottom_sheets.add_remote_con.octoeverywehre.tab_name', + if (octoIndex == 0) + Tab( + text: tr( + 'bottom_sheets.add_remote_con.octoeverywehre.tab_name', + ), ), - ), if (obicoEnabled) Tab( text: tr( 'bottom_sheets.add_remote_con.obico.service_name', ), ), + if (octoIndex == 1) + Tab( + text: tr( + 'bottom_sheets.add_remote_con.octoeverywehre.tab_name', + ), + ), Tab( text: tr('bottom_sheets.add_remote_con.manual.tab_name'), ), diff --git a/lib/ui/components/bottomsheet/remote_connection/add_remote_connection_bottom_sheet_controller.dart b/lib/ui/components/bottomsheet/remote_connection/add_remote_connection_bottom_sheet_controller.dart index 13d776416..ba9c315b0 100644 --- a/lib/ui/components/bottomsheet/remote_connection/add_remote_connection_bottom_sheet_controller.dart +++ b/lib/ui/components/bottomsheet/remote_connection/add_remote_connection_bottom_sheet_controller.dart @@ -20,6 +20,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../screens/printers/components/http_headers.dart'; @@ -33,17 +34,9 @@ GlobalKey formKey(FormKeyRef _) { } @Riverpod(dependencies: []) -AddRemoteConnectionSheetArgs sheetArgs(SheetArgsRef _) { - throw UnimplementedError(); -} +AddRemoteConnectionSheetArgs sheetArgs(Ref _) => throw UnimplementedError(); -@Riverpod(dependencies: [ - sheetArgs, - goRouter, - machineService, - snackBarService, - dialogService, -]) +@Riverpod(dependencies: [sheetArgs]) class AddRemoteConnectionBottomSheetController extends _$AddRemoteConnectionBottomSheetController { FormBuilderState get _formState => ref.read(formKeyProvider).currentState!; diff --git a/lib/ui/screens/printers/add/printers_add_page.dart b/lib/ui/screens/printers/add/printers_add_page.dart index 27780ed27..9b6a4b888 100644 --- a/lib/ui/screens/printers/add/printers_add_page.dart +++ b/lib/ui/screens/printers/add/printers_add_page.dart @@ -180,6 +180,34 @@ class _InputModeStepScreen extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { var controller = ref.watch(printerAddViewControllerProvider.notifier); + // Randomize how its shown to the user for fairness + final oneClick3rdParty = [ + Align( + alignment: Alignment.center, + child: TextButton.icon( + onPressed: controller.addFromOcto, + icon: SvgPicture.asset( + 'assets/vector/oe_rocket.svg', + width: 24, + height: 24, + ), + label: const Text('pages.printer_add.select_mode.add_via_oe').tr(), + ), + ), + Align( + alignment: Alignment.center, + child: TextButton.icon( + onPressed: controller.addFromObico, + icon: SvgPicture.asset( + 'assets/vector/obico_logo.svg', + width: 24, + height: 24, + ), + label: const Text('pages.printer_add.select_mode.add_via_obico').tr(), + ), + ), + ]..shuffle(); + var themeData = Theme.of(context); return Column( mainAxisAlignment: MainAxisAlignment.center, @@ -223,32 +251,7 @@ class _InputModeStepScreen extends ConsumerWidget { ), ], ), - Align( - alignment: Alignment.center, - child: TextButton.icon( - onPressed: controller.addFromOcto, - icon: SvgPicture.asset( - 'assets/vector/oe_rocket.svg', - width: 24, - height: 24, - ), - label: const Text('pages.printer_add.select_mode.add_via_oe').tr(), - ), - ), - Align( - alignment: Alignment.center, - child: TextButton.icon( - onPressed: controller.addFromObico, - icon: SvgPicture.asset( - 'assets/vector/obico_logo.svg', - width: 24, - height: 24, - ), - label: const Text('pages.printer_add.select_mode.add_via_obico').tr(), - ), - ), - // OctoEveryWhereBtn( - // title: 'Add using OctoEverywhere', onPressed: () => null), + ...oneClick3rdParty, ], ); } From ea9317092c9d98565f49484d9299bec1a16bc318 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Wed, 20 Nov 2024 22:45:15 +0100 Subject: [PATCH 28/64] doc: Added RemoteService order randomization to changelog --- docs/changelog.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 1b0e56c83..23e2550b7 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,24 +4,24 @@ ### Enhancements -- **Macro Visibility**: It is now possible to configure the actual printer state for which a macro should be visible. - This - allows users to hide macros that are not relevant for the current printer state. To adjust the visibility of macros, - just tap on the macro within the group editor of the printer edit page. - -### Bug Fixes - -- **GCode-Preview**: Fixed an issue within the GCode-Parser that caused the preview to show an error if the - `SET_RETRACTION` was used in the GCode file. -- **Object Naming**: Klipper objects (Config entries) of the same class with the same name no longer cause the app to - show a permanent error message. This could happen if the user configured different kind of fans, e.g. a `fan_generic` - and `temperature_fan`, with the same name. The app is now aware of the object kind and will allow users to have - objects of the same class with - the same name but different kind, applies to fans, leds and filament sensors. -- **WebRtc Cam Creation**: Fixed an issue in the code that handles the creation of WebRtc cams in the app's internals. - This caused to many webRTC cam streams to be opened in a short time, which could lead to a crash of the app. -- **Obico One Click Setup**: Fixed an issue where the app would not allow to setup an Obico connection with a single - click via the app page due to an issue with the used http client. +- **Macro Visibility**: Added configurable printer state visibility for macros. Users can now hide macros irrelevant to + the current printer state by tapping the macro in the group editor on the printer edit page. + +- **Remote Services Randomization**: Randomized the order of remote services on the printer add and edit pages to ensure + a more fair representation. + +### Bug Fixes + +- **GCode Preview**: Fixed a parser error in the GCode preview when `SET_RETRACTION` command is used. + +- **Object Naming**: Resolved an issue with Klipper objects (Config entries) of the same class and name. The app now + distinguishes between different object kinds (e.g., `fan_generic` and `temperature_fan`) when preventing naming + conflicts. + +- **WebRTC Camera Streams**: Fixed a stability issue that could cause app crashes by opening too many WebRTC camera + streams simultaneously. + +- **Obico One-Click Setup**: Corrected an HTTP client-related problem preventing single-click Obico connection setup. ## [2.8.2] - 2024-10-31 From e736c2579ea36366502e1429deae0f27eab6bf00 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Wed, 20 Nov 2024 22:56:03 +0100 Subject: [PATCH 29/64] fix: Usage of riverpod.dependencies --- .../details/gcode_file_details_page.dart | 54 ++++++++----------- .../screens/fullcam/full_cam_controller.dart | 2 +- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/lib/ui/screens/files/details/gcode_file_details_page.dart b/lib/ui/screens/files/details/gcode_file_details_page.dart index df896b5c9..5155d46b0 100644 --- a/lib/ui/screens/files/details/gcode_file_details_page.dart +++ b/lib/ui/screens/files/details/gcode_file_details_page.dart @@ -54,7 +54,7 @@ class GCodeFileDetailPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return ProviderScope( - overrides: [gcodeProvider.overrideWithValue(gcodeFile)], + overrides: [_gcodeProvider.overrideWithValue(gcodeFile)], child: const _GCodeFileDetailPage(), ); } @@ -103,7 +103,6 @@ class _CompactBody extends HookConsumerWidget { return SliverAppBar( expandedHeight: 220, floating: true, - actions: const [_PreHeatBtn()], flexibleSpace: FlexibleSpaceBar( collapseMode: CollapseMode.pin, background: Stack(alignment: Alignment.center, children: [ @@ -493,10 +492,7 @@ class _AppBar extends ConsumerWidget implements PreferredSizeWidget { Widget build(BuildContext context, WidgetRef ref) { final model = ref.watch(_gCodeFileDetailsControllerProvider); - return AppBar( - title: Text(model.file.name), - actions: const [_PreHeatBtn()], - ); + return AppBar(title: Text(model.file.name)); } @override @@ -511,6 +507,17 @@ class _Fab extends ConsumerWidget { var controller = ref.watch(_gCodeFileDetailsControllerProvider.notifier); var canStartPrint = ref.watch(_gCodeFileDetailsControllerProvider.select((data) => data.canStartPrint)); + return FloatingActionButton.extended( + icon: const Icon(Icons.more_vert), + label: const Text('pages.files.details.actions').tr(), + onPressed: () { + final box = context.findRenderObject() as RenderBox?; + final pos = box!.localToGlobal(Offset.zero) & box.size; + + controller.onActionsTap(pos); + }, + ); + var themeData = Theme.of(context); return FloatingActionButton.extended( backgroundColor: (canStartPrint) ? null : themeData.disabledColor, @@ -521,23 +528,6 @@ class _Fab extends ConsumerWidget { } } -class _PreHeatBtn extends ConsumerWidget { - const _PreHeatBtn({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - var controller = ref.watch(_gCodeFileDetailsControllerProvider.notifier); - var canPreheat = ref.watch(_gCodeFileDetailsControllerProvider - .select((data) => data.canStartPrint && data.file.firstLayerTempBed != null)); - - return IconButton( - onPressed: canPreheat ? controller.onPreHeatPrinterTap : null, - icon: const Icon(MobilerakerIcons.nozzle_heat), - tooltip: 'pages.files.details.preheat'.tr(), - ); - } -} - class _PropertyTile extends StatelessWidget { final String title; final String subtitle; @@ -564,9 +554,9 @@ class _PropertyTile extends StatelessWidget { } @Riverpod(dependencies: []) -GCodeFile gcode(Ref ref) => throw UnimplementedError(); +GCodeFile _gcode(Ref ref) => throw UnimplementedError(); -@Riverpod(dependencies: [gcode]) +@Riverpod(dependencies: [_gcode]) class _GCodeFileDetailsController extends _$GCodeFileDetailsController { @override _Model build() { @@ -575,7 +565,7 @@ class _GCodeFileDetailsController extends _$GCodeFileDetailsController { canPrintCalc(PrintState? d) => d != null && (d != PrintState.printing || d != PrintState.paused); final machineUUID = ref.watch(selectedMachineProvider.selectRequireValue((value) => value!.uuid)); - final gCodeFile = ref.watch(gcodeProvider); + final gCodeFile = ref.watch(_gcodeProvider); final klippy = ref.watch(klipperProvider(machineUUID)).valueOrNull; final printer = ref.read(printerProvider(machineUUID)).valueOrNull; ref.listen(printerProvider(machineUUID), (previous, next) { @@ -638,13 +628,15 @@ class _GCodeFileDetailsController extends _$GCodeFileDetailsController { SnackBarService get _snackBarService => ref.read(snackBarServiceProvider); - onStartPrintTap() { - _printerService.startPrintFile(ref.read(gcodeProvider)); - ref.read(goRouterProvider).goNamed(AppRoute.dashBoard.name); + GoRouter get _goRouter => ref.read(goRouterProvider); + + void onStartPrintTap() { + _printerService.startPrintFile(ref.read(_gcodeProvider)); + _goRouter.goNamed(AppRoute.dashBoard.name); } - onPreHeatPrinterTap() { - var gCodeFile = ref.read(gcodeProvider); + void onPreHeatPrinterTap() { + var gCodeFile = ref.read(_gcodeProvider); var tempArgs = [ '170', gCodeFile.firstLayerTempBed?.toStringAsFixed(0) ?? '60', diff --git a/lib/ui/screens/fullcam/full_cam_controller.dart b/lib/ui/screens/fullcam/full_cam_controller.dart index 4b5fc2e61..c4b5d5f08 100644 --- a/lib/ui/screens/fullcam/full_cam_controller.dart +++ b/lib/ui/screens/fullcam/full_cam_controller.dart @@ -18,7 +18,7 @@ Machine fullCamMachine(Ref ref) => throw UnimplementedError(); @Riverpod(dependencies: []) WebcamInfo initialCam(Ref ref) => throw UnimplementedError(); -@Riverpod(dependencies: [fullCamMachine, initialCam, settingService]) +@Riverpod(dependencies: [initialCam]) class FullCamPageController extends _$FullCamPageController { @override WebcamInfo build() { From 3bea6280317e6f020b72709c2211f6e301ee0d47 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Thu, 21 Nov 2024 22:56:56 +0100 Subject: [PATCH 30/64] fix: Missing import --- lib/ui/screens/files/details/gcode_file_details_page.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ui/screens/files/details/gcode_file_details_page.dart b/lib/ui/screens/files/details/gcode_file_details_page.dart index 5155d46b0..4273bfaa0 100644 --- a/lib/ui/screens/files/details/gcode_file_details_page.dart +++ b/lib/ui/screens/files/details/gcode_file_details_page.dart @@ -35,6 +35,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_icons/flutter_icons.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mobileraker_pro/service/ui/pro_sheet_type.dart'; import 'package:mobileraker_pro/spoolman/dto/get_spool.dart'; From b671cce37aa58618caf8b57f5e5ed878bbdb95fa Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Thu, 21 Nov 2024 22:58:10 +0100 Subject: [PATCH 31/64] fix: Removed dead code --- .../details/gcode_file_details_page.dart | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/ui/screens/files/details/gcode_file_details_page.dart b/lib/ui/screens/files/details/gcode_file_details_page.dart index 4273bfaa0..2dc8d8d32 100644 --- a/lib/ui/screens/files/details/gcode_file_details_page.dart +++ b/lib/ui/screens/files/details/gcode_file_details_page.dart @@ -508,16 +508,16 @@ class _Fab extends ConsumerWidget { var controller = ref.watch(_gCodeFileDetailsControllerProvider.notifier); var canStartPrint = ref.watch(_gCodeFileDetailsControllerProvider.select((data) => data.canStartPrint)); - return FloatingActionButton.extended( - icon: const Icon(Icons.more_vert), - label: const Text('pages.files.details.actions').tr(), - onPressed: () { - final box = context.findRenderObject() as RenderBox?; - final pos = box!.localToGlobal(Offset.zero) & box.size; - - controller.onActionsTap(pos); - }, - ); + // return FloatingActionButton.extended( + // icon: const Icon(Icons.more_vert), + // label: const Text('pages.files.details.actions').tr(), + // onPressed: () { + // final box = context.findRenderObject() as RenderBox?; + // final pos = box!.localToGlobal(Offset.zero) & box.size; + // + // controller.onActionsTap(pos); + // }, + // ); var themeData = Theme.of(context); return FloatingActionButton.extended( From ca55e1b669fe936ae54494629952a433570e5a83 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 23 Nov 2024 12:10:14 +0100 Subject: [PATCH 32/64] fix: Android build --- android/app/build.gradle | 42 +++++++-------------- android/app/src/debug/AndroidManifest.xml | 3 +- android/app/src/main/AndroidManifest.xml | 15 ++++++-- android/app/src/profile/AndroidManifest.xml | 3 +- android/gradle.properties | 6 ++- android/settings.gradle | 7 ++-- 6 files changed, 34 insertions(+), 42 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index e680dd6f1..35b7e8702 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -22,18 +22,6 @@ if (localPropertiesFile.exists()) { } } - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - - def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') if (keystorePropertiesFile.exists()) { @@ -41,30 +29,26 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdk flutter.compileSdkVersion - ndkVersion flutter.ndkVersion + namespace = 'com.mobileraker' + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.mobileraker.android" -// minSdkVersion flutter.minSdkVersion - minSdkVersion 25 - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + applicationId = "com.mobileraker.android" + minSdk = 25 + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName } signingConfigs { @@ -78,16 +62,16 @@ android { buildTypes { release { - signingConfig signingConfigs.release + signingConfig = signingConfigs.release } } } flutter { - source '../..' + source = "../.." } dependencies { - implementation platform('com.google.firebase:firebase-bom:33.0.0') + implementation platform('com.google.firebase:firebase-bom:33.6.0') implementation('com.google.firebase:firebase-analytics') } diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 7fffffcad..f880684a6 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index dce3b0f2e..9cc6a23fe 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,10 +1,9 @@ - + - + + + + + + + + @@ -43,6 +49,7 @@ android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" + android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index 7fffffcad..f880684a6 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/android/gradle.properties b/android/gradle.properties index 94adc3a3f..15ee4235e 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,7 @@ -org.gradle.jvmargs=-Xmx1536M +# +# Copyright (c) 2024. Patrick Schmidt. +# All rights reserved. +# +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/android/settings.gradle b/android/settings.gradle index 8ba491344..8049ddf91 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -10,10 +10,9 @@ pluginManagement { def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" return flutterSdkPath - } - settings.ext.flutterSdkPath = flutterSdkPath() + }() - includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") repositories { google() @@ -32,4 +31,4 @@ plugins { id "com.google.firebase.crashlytics" version "2.9.9" apply false } -include ":app" +include ":app" \ No newline at end of file From 9fbd4289d7a088554fed2dc45b010db6479e69b3 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 23 Nov 2024 13:01:57 +0100 Subject: [PATCH 33/64] build: Upgraded android target to JDK 17 --- android/app/build.gradle | 6 +- android/build.gradle | 13 ++ android/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.properties | 7 +- android/settings.gradle | 2 +- common/pubspec.yaml | 20 +- lib/routing/app_router.dart | 11 +- .../files/details/video_player_page.dart | 204 ------------------ pubspec.yaml | 28 ++- 9 files changed, 51 insertions(+), 241 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 35b7e8702..bb7e6f813 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -34,12 +34,12 @@ android { ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } defaultConfig { diff --git a/android/build.gradle b/android/build.gradle index 27b5721dc..575df6f95 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -34,6 +34,19 @@ subprojects { } } } + if (project.hasProperty('android')) { + project.android { + if (namespace == null) { + project.logger.warn( + "Warning: No namespace defined for project: " + + project.name + + ". Automatically setting namespace to project group: " + + project.group + ) + namespace project.group + } + } + } } project.evaluationDependsOn(':app') diff --git a/android/gradle.properties b/android/gradle.properties index 15ee4235e..b5850a089 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -5,3 +5,4 @@ org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true +kotlin.jvm.target.validation.mode=IGNORE \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index cc5527d78..cb6344254 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,11 @@ +# +# Copyright (c) 2024. Patrick Schmidt. +# All rights reserved. +# + #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 8049ddf91..7965e4175 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -23,7 +23,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.3.0" apply false + id "com.android.application" version "8.1.0" apply false // START: FlutterFire Configuration id "com.google.gms.google-services" version "4.3.15" apply false // END: FlutterFire Configuration diff --git a/common/pubspec.yaml b/common/pubspec.yaml index b6fd9fbe3..9a7c2816e 100644 --- a/common/pubspec.yaml +++ b/common/pubspec.yaml @@ -24,21 +24,21 @@ dependencies: #firebase firebase_core_platform_interface: ^5.3.0 - firebase_core: ^2.30.0 - firebase_analytics: ^10.10.2 - firebase_app_check: ^0.2.2+2 - firebase_crashlytics: ^3.5.2 - firebase_remote_config: ^4.4.2 - cloud_firestore: ^4.17.0 - firebase_auth: ^4.19.2 + firebase_core: ^3.6.0 + firebase_analytics: ^11.3.5 + firebase_app_check: ^0.3.1+6 + firebase_crashlytics: ^4.1.5 + firebase_remote_config: ^5.1.5 + cloud_firestore: ^5.5.0 + firebase_auth: ^5.3.3 #purchases purchases_flutter: ^8.1.2 #notification - awesome_notifications_core: ^0.9.3 - awesome_notifications: ^0.9.3 - awesome_notifications_fcm: ^0.9.3 + awesome_notifications_core: ^0.10.0 + awesome_notifications: ^0.10.0 + awesome_notifications_fcm: ^0.10.0 # live_activities: ^1.9.3 live_activities: git: diff --git a/lib/routing/app_router.dart b/lib/routing/app_router.dart index e7e62362c..676dd5faf 100644 --- a/lib/routing/app_router.dart +++ b/lib/routing/app_router.dart @@ -52,7 +52,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:smooth_sheets/smooth_sheets.dart'; import '../ui/screens/dashboard/customizable_dashboard_page.dart'; -import '../ui/screens/files/details/video_player_page.dart'; import '../ui/screens/files/move_file_destination_page.dart'; import '../ui/screens/spoolman/filament_form_page.dart'; import '../ui/screens/spoolman/spool_form_page.dart'; @@ -212,11 +211,11 @@ GoRouter goRouterImpl(GoRouterRef ref) { name: AppRoute.fileManager_exlorer_editor.name, builder: (context, state) => ConfigFileDetailPage(file: state.extra! as GenericFile), ), - GoRoute( - path: 'video-player', - name: AppRoute.fileManager_exlorer_videoPlayer.name, - builder: (context, state) => VideoPlayerPage(state.extra! as GenericFile), - ), + // GoRoute( + // path: 'video-player', + // name: AppRoute.fileManager_exlorer_videoPlayer.name, + // builder: (context, state) => VideoPlayerPage(state.extra! as GenericFile), + // ), GoRoute( path: 'image-viewer', name: AppRoute.fileManager_exlorer_imageViewer.name, diff --git a/lib/ui/screens/files/details/video_player_page.dart b/lib/ui/screens/files/details/video_player_page.dart index fd5027142..d72b35f9e 100644 --- a/lib/ui/screens/files/details/video_player_page.dart +++ b/lib/ui/screens/files/details/video_player_page.dart @@ -2,207 +2,3 @@ * Copyright (c) 2023-2024. Patrick Schmidt. * All rights reserved. */ - -import 'dart:async'; - -import 'package:appinio_video_player/appinio_video_player.dart'; -import 'package:common/data/dto/files/generic_file.dart'; -import 'package:common/data/model/file_operation.dart'; -import 'package:common/network/dio_provider.dart'; -import 'package:common/service/moonraker/file_service.dart'; -import 'package:common/service/payment_service.dart'; -import 'package:common/service/selected_machine_service.dart'; -import 'package:common/service/ui/snackbar_service_interface.dart'; -import 'package:common/ui/components/error_card.dart'; -import 'package:common/ui/components/supporter_only_feature.dart'; -import 'package:common/util/extensions/remote_file_extension.dart'; -import 'package:common/util/logger.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:share_plus/share_plus.dart'; - -class VideoPlayerPage extends ConsumerStatefulWidget { - final GenericFile file; - - const VideoPlayerPage(this.file, {super.key}); - - @override - ConsumerState createState() => _VideoPlayerPageState(); -} - -class _VideoPlayerPageState extends ConsumerState { - late CachedVideoPlayerController videoPlayerController; - late CustomVideoPlayerController _customVideoPlayerController; - bool loading = true; - double? fileDownloadProgress; - String? error; - StreamSubscription? downloadStreamSub; - - @override - void initState() { - super.initState(); - var machine = ref.read(selectedMachineProvider).requireValue!; - var dio = ref.read(dioClientProvider(machine.uuid)); - var fileUri = widget.file.downloadUri(Uri.tryParse(dio.options.baseUrl))!; - - Map headers = dio.options.headers.cast(); - - videoPlayerController = CachedVideoPlayerController.network(fileUri.toString(), httpHeaders: headers) - ..initialize() - .then( - (value) => setState(() { - loading = false; - videoPlayerController.play(); - }), - ) - .catchError((err) { - setState(() { - logger.w('Could not load video File...', err); - loading = false; - error = err.toString(); - }); - }); - - _customVideoPlayerController = CustomVideoPlayerController( - context: context, - videoPlayerController: videoPlayerController, - ); - } - - @override - Widget build(BuildContext context) { - Widget body; - if (loading) { - if (fileDownloadProgress != null && !ref.watch(isSupporterProvider)) { - body = Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SupporterOnlyFeature( - text: const Text('components.supporter_only_feature.timelaps_share').tr(), - ), - ElevatedButton( - onPressed: () => setState(() { - fileDownloadProgress = null; - loading = false; - }), - child: Text(MaterialLocalizations.of(context).backButtonTooltip), - ), - ], - ); - } else if (fileDownloadProgress != null) { - var percent = NumberFormat.percentPattern(context.locale.toStringWithSeparator()).format(fileDownloadProgress); - body = Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: CircularProgressIndicator.adaptive( - value: fileDownloadProgress, - ), - ), - const Text('pages.video_player.downloading_for_sharing').tr(args: [percent]), - ], - ); - } else { - body = const Center(child: CircularProgressIndicator()); - } - } else if (error != null) { - body = ErrorCard( - title: const Text('Could not load video File...'), - body: Text('Error while loading file: $error'), - ); - } else { - body = CustomVideoPlayer( - customVideoPlayerController: _customVideoPlayerController, - ); - } - - return Scaffold( - appBar: AppBar( - title: Text(widget.file.name), - actions: [ - Builder(builder: (context) { - return IconButton( - onPressed: loading ? null : () => _startDownload(context), - icon: const Icon(Icons.share), - ); - }), - ], - ), - body: SafeArea(child: SizedBox.expand(child: body)), - ); - } - - _startDownload(BuildContext ctx) { - var isSupporter = ref.read(isSupporterProvider); - - final box = ctx.findRenderObject() as RenderBox?; - final pos = box!.localToGlobal(Offset.zero) & box.size; - - setState(() { - loading = true; - fileDownloadProgress = 0; - }); - - if (!isSupporter) { - return; - } - - downloadStreamSub?.cancel(); - downloadStreamSub = ref.read(fileServiceSelectedProvider).downloadFile(filePath: widget.file.absolutPath).listen( - (event) async { - if (event is FileOperationProgress) { - setState(() { - fileDownloadProgress = event.progress; - }); - return; - } - var downloadFile = event as FileDownloadComplete; - // logger.i('File in FS is at ${file.absolute.path}'); - logger.i( - 'File in FS is at ${downloadFile.file.absolute.path}, size : ${downloadFile.file.lengthSync()}', - ); - setState(() { - fileDownloadProgress = 1; - }); - - await Share.shareXFiles( - [XFile(downloadFile.file.path, mimeType: 'video/mp4')], - subject: 'Video ${widget.file.name}', - sharePositionOrigin: pos, - ); - logger.i('Done with sharing'); - setState(() { - fileDownloadProgress = null; - loading = false; - }); - }, - onError: (e) { - ref.read(snackBarServiceProvider).show(SnackBarConfig( - type: SnackbarType.error, - title: 'Error while downloading file for sharing.', - message: e.toString(), - )); - setState(() { - fileDownloadProgress = null; - loading = false; - }); - }, - onDone: () { - logger.i('File Dowload is completed'); - }, - ); - - // var fileInFs = await downloadFile.copy('${tmpDir.path}/${downloadFile.path}'); - } - - @override - void dispose() { - downloadStreamSub?.cancel(); - _customVideoPlayerController.dispose(); - videoPlayerController.dispose(); - super.dispose(); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index f4b07d07a..b484879f0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,13 +23,9 @@ environment: dependency_overrides: - intl: ^0.18.1 # required due to awesome notifications and formbuilder web: ^0.5.1 # required because network info is using 0.3, but this package is only used by web which we dont build # used by appinio_video_player, this fixes an issue with gradle - cached_video_player: - git: - url: https://github.com/vikram25897/flutter_cached_video_player - ref: feature/gradle_version_bump + dependencies: flutter: @@ -44,13 +40,13 @@ dependencies: #firebase firebase_core_platform_interface: ^5.3.0 - firebase_core: ^2.30.0 - firebase_analytics: ^10.10.2 - firebase_app_check: ^0.2.2+2 - firebase_crashlytics: ^3.5.2 - firebase_remote_config: ^4.4.2 - cloud_firestore: ^4.17.0 - firebase_auth: ^4.19.2 + firebase_core: ^3.6.0 + firebase_analytics: ^11.3.5 + firebase_app_check: ^0.3.1+6 + firebase_crashlytics: ^4.1.5 + firebase_remote_config: ^5.1.5 + cloud_firestore: ^5.5.0 + firebase_auth: ^5.3.3 #architecture freezed_annotation: ^2.4.1 @@ -93,9 +89,9 @@ dependencies: google_fonts: ^6.2.0 # 6.2.0 preventing the app from building #notification - awesome_notifications_core: ^0.9.3 - awesome_notifications: ^0.9.3 - awesome_notifications_fcm: ^0.9.3 + awesome_notifications_core: ^0.10.0 + awesome_notifications: ^0.10.0 + awesome_notifications_fcm: ^0.10.0 # live_activities: ^1.9.3 live_activities: # path: ../flutter_live_activities @@ -162,7 +158,7 @@ dependencies: flutter_markdown: ^0.7.3+1 flutter_svg: ^2.0.5 loader_overlay: ^4.0.0 - appinio_video_player: ^1.3.0 + # appinio_video_player: ^1.3.0 file_picker: ^8.0.7 geekyants_flutter_gauges: ^1.0.3 pretty_qr_code: ^3.3.0 From f059cc761c799f8c0512bf6d39b365c08e6b4334 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 23 Nov 2024 13:15:41 +0100 Subject: [PATCH 34/64] fix: Video Player Page --- lib/routing/app_router.dart | 11 +- .../files/details/video_player_page.dart | 204 ++++++++++++++++++ pubspec.yaml | 8 +- 3 files changed, 215 insertions(+), 8 deletions(-) diff --git a/lib/routing/app_router.dart b/lib/routing/app_router.dart index 676dd5faf..e7e62362c 100644 --- a/lib/routing/app_router.dart +++ b/lib/routing/app_router.dart @@ -52,6 +52,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:smooth_sheets/smooth_sheets.dart'; import '../ui/screens/dashboard/customizable_dashboard_page.dart'; +import '../ui/screens/files/details/video_player_page.dart'; import '../ui/screens/files/move_file_destination_page.dart'; import '../ui/screens/spoolman/filament_form_page.dart'; import '../ui/screens/spoolman/spool_form_page.dart'; @@ -211,11 +212,11 @@ GoRouter goRouterImpl(GoRouterRef ref) { name: AppRoute.fileManager_exlorer_editor.name, builder: (context, state) => ConfigFileDetailPage(file: state.extra! as GenericFile), ), - // GoRoute( - // path: 'video-player', - // name: AppRoute.fileManager_exlorer_videoPlayer.name, - // builder: (context, state) => VideoPlayerPage(state.extra! as GenericFile), - // ), + GoRoute( + path: 'video-player', + name: AppRoute.fileManager_exlorer_videoPlayer.name, + builder: (context, state) => VideoPlayerPage(state.extra! as GenericFile), + ), GoRoute( path: 'image-viewer', name: AppRoute.fileManager_exlorer_imageViewer.name, diff --git a/lib/ui/screens/files/details/video_player_page.dart b/lib/ui/screens/files/details/video_player_page.dart index d72b35f9e..dfdc517cb 100644 --- a/lib/ui/screens/files/details/video_player_page.dart +++ b/lib/ui/screens/files/details/video_player_page.dart @@ -2,3 +2,207 @@ * Copyright (c) 2023-2024. Patrick Schmidt. * All rights reserved. */ + +import 'dart:async'; + +import 'package:appinio_video_player/appinio_video_player.dart'; +import 'package:common/data/dto/files/generic_file.dart'; +import 'package:common/data/model/file_operation.dart'; +import 'package:common/network/dio_provider.dart'; +import 'package:common/service/moonraker/file_service.dart'; +import 'package:common/service/payment_service.dart'; +import 'package:common/service/selected_machine_service.dart'; +import 'package:common/service/ui/snackbar_service_interface.dart'; +import 'package:common/ui/components/error_card.dart'; +import 'package:common/ui/components/supporter_only_feature.dart'; +import 'package:common/util/extensions/remote_file_extension.dart'; +import 'package:common/util/logger.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:share_plus/share_plus.dart'; + +class VideoPlayerPage extends ConsumerStatefulWidget { + final GenericFile file; + + const VideoPlayerPage(this.file, {super.key}); + + @override + ConsumerState createState() => _VideoPlayerPageState(); +} + +class _VideoPlayerPageState extends ConsumerState { + late CachedVideoPlayerPlusController videoPlayerController; + late CustomVideoPlayerController _customVideoPlayerController; + bool loading = true; + double? fileDownloadProgress; + String? error; + StreamSubscription? downloadStreamSub; + + @override + void initState() { + super.initState(); + var machine = ref.read(selectedMachineProvider).requireValue!; + var dio = ref.read(dioClientProvider(machine.uuid)); + var fileUri = widget.file.downloadUri(Uri.tryParse(dio.options.baseUrl))!; + + Map headers = dio.options.headers.cast(); + + videoPlayerController = CachedVideoPlayerPlusController.networkUrl(fileUri, httpHeaders: headers) + ..initialize() + .then( + (value) => setState(() { + loading = false; + videoPlayerController.play(); + }), + ) + .catchError((err) { + setState(() { + logger.w('Could not load video File...', err); + loading = false; + error = err.toString(); + }); + }); + + _customVideoPlayerController = CustomVideoPlayerController( + context: context, + videoPlayerController: videoPlayerController, + ); + } + + @override + Widget build(BuildContext context) { + Widget body; + if (loading) { + if (fileDownloadProgress != null && !ref.watch(isSupporterProvider)) { + body = Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SupporterOnlyFeature( + text: const Text('components.supporter_only_feature.timelaps_share').tr(), + ), + ElevatedButton( + onPressed: () => setState(() { + fileDownloadProgress = null; + loading = false; + }), + child: Text(MaterialLocalizations.of(context).backButtonTooltip), + ), + ], + ); + } else if (fileDownloadProgress != null) { + var percent = NumberFormat.percentPattern(context.locale.toStringWithSeparator()).format(fileDownloadProgress); + body = Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: CircularProgressIndicator.adaptive( + value: fileDownloadProgress, + ), + ), + const Text('pages.video_player.downloading_for_sharing').tr(args: [percent]), + ], + ); + } else { + body = const Center(child: CircularProgressIndicator()); + } + } else if (error != null) { + body = ErrorCard( + title: const Text('Could not load video File...'), + body: Text('Error while loading file: $error'), + ); + } else { + body = CustomVideoPlayer( + customVideoPlayerController: _customVideoPlayerController, + ); + } + + return Scaffold( + appBar: AppBar( + title: Text(widget.file.name), + actions: [ + Builder(builder: (context) { + return IconButton( + onPressed: loading ? null : () => _startDownload(context), + icon: const Icon(Icons.share), + ); + }), + ], + ), + body: SafeArea(child: SizedBox.expand(child: body)), + ); + } + + _startDownload(BuildContext ctx) { + var isSupporter = ref.read(isSupporterProvider); + + final box = ctx.findRenderObject() as RenderBox?; + final pos = box!.localToGlobal(Offset.zero) & box.size; + + setState(() { + loading = true; + fileDownloadProgress = 0; + }); + + if (!isSupporter) { + return; + } + + downloadStreamSub?.cancel(); + downloadStreamSub = ref.read(fileServiceSelectedProvider).downloadFile(filePath: widget.file.absolutPath).listen( + (event) async { + if (event is FileOperationProgress) { + setState(() { + fileDownloadProgress = event.progress; + }); + return; + } + var downloadFile = event as FileDownloadComplete; + // logger.i('File in FS is at ${file.absolute.path}'); + logger.i( + 'File in FS is at ${downloadFile.file.absolute.path}, size : ${downloadFile.file.lengthSync()}', + ); + setState(() { + fileDownloadProgress = 1; + }); + + await Share.shareXFiles( + [XFile(downloadFile.file.path, mimeType: 'video/mp4')], + subject: 'Video ${widget.file.name}', + sharePositionOrigin: pos, + ); + logger.i('Done with sharing'); + setState(() { + fileDownloadProgress = null; + loading = false; + }); + }, + onError: (e) { + ref.read(snackBarServiceProvider).show(SnackBarConfig( + type: SnackbarType.error, + title: 'Error while downloading file for sharing.', + message: e.toString(), + )); + setState(() { + fileDownloadProgress = null; + loading = false; + }); + }, + onDone: () { + logger.i('File Dowload is completed'); + }, + ); + + // var fileInFs = await downloadFile.copy('${tmpDir.path}/${downloadFile.path}'); + } + + @override + void dispose() { + downloadStreamSub?.cancel(); + _customVideoPlayerController.dispose(); + videoPlayerController.dispose(); + super.dispose(); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index b484879f0..99147a437 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,8 +25,6 @@ environment: dependency_overrides: web: ^0.5.1 # required because network info is using 0.3, but this package is only used by web which we dont build # used by appinio_video_player, this fixes an issue with gradle - - dependencies: flutter: sdk: flutter @@ -158,7 +156,11 @@ dependencies: flutter_markdown: ^0.7.3+1 flutter_svg: ^2.0.5 loader_overlay: ^4.0.0 - # appinio_video_player: ^1.3.0 + appinio_video_player: + git: + url: https://github.com/JahnChoi/appinio_flutter_packages + path: packages/appinio_video_player + ref: feature/switch-cached-video-player-dep file_picker: ^8.0.7 geekyants_flutter_gauges: ^1.0.3 pretty_qr_code: ^3.3.0 From 7477ab2ed8582001740356572183089d637fb858 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 23 Nov 2024 13:22:23 +0100 Subject: [PATCH 35/64] build: Upgraded more android dependencies --- android/settings.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/android/settings.gradle b/android/settings.gradle index 7965e4175..74320c0e7 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -23,12 +23,12 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.1.0" apply false + id "com.android.application" version "8.1.4" apply false // START: FlutterFire Configuration - id "com.google.gms.google-services" version "4.3.15" apply false + id "com.google.gms.google-services" version "4.4.2" apply false // END: FlutterFire Configuration id "org.jetbrains.kotlin.android" version "2.0.20" apply false - id "com.google.firebase.crashlytics" version "2.9.9" apply false + id "com.google.firebase.crashlytics" version "3.0.2" apply false } include ":app" \ No newline at end of file From eb6298dfe335199abbfca9e1633fabf261545fb9 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 23 Nov 2024 14:19:46 +0100 Subject: [PATCH 36/64] build: Upgraded more flutter dependencies --- common/pubspec.yaml | 36 ++++++++++++++++++------------------ pubspec.yaml | 19 ++++++++++--------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/common/pubspec.yaml b/common/pubspec.yaml index 9a7c2816e..489e89215 100644 --- a/common/pubspec.yaml +++ b/common/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependency_overrides: web: ^0.5.1 # required because network info is using 0.3, but this package is only used by web which we dont build - + http_parser: ^4.0.2 # Required because dio_smart_retry is in conflict with the flutter sdk otherwise dependencies: flutter: @@ -18,8 +18,8 @@ dependencies: #network web_socket_channel: ^3.0.1 dio: ^5.3.3 - dio_smart_retry: ^6.0.0 - flutter_web_auth: ^0.5.0 + dio_smart_retry: ^7.0.1 + flutter_web_auth: ^0.6.0 http: ^1.2.1 #firebase @@ -33,7 +33,7 @@ dependencies: firebase_auth: ^5.3.3 #purchases - purchases_flutter: ^8.1.2 + purchases_flutter: ^8.2.2 #notification awesome_notifications_core: ^0.10.0 @@ -62,8 +62,8 @@ dependencies: easy_localization: ^3.0.3 #persisstent - path_provider: ^2.1.0 - hive_ce: ^2.6.0 + path_provider: ^2.1.5 + hive_ce: ^2.8.0+1 hive_ce_flutter: ^2.1.0 flutter_cache_manager: ^3.3.1 @@ -71,12 +71,12 @@ dependencies: responsive_framework: ^1.4.0 #bottomsheet: - smooth_sheets: ^1.0.0-f324.0.10.1 + smooth_sheets: ^1.0.0-f324.0.10.2 #ui flutter_icons: git: https://github.com/jibiel/flutter-icons.git - flutter_svg: ^2.0.5 + flutter_svg: ^2.0.15 progress_indicators: ^1.0.0 #misc @@ -85,30 +85,30 @@ dependencies: stringr: ^1.0.0 collection: ^1.18.0 uuid: ^4.3.3 - network_info_plus: ^6.0.1 + network_info_plus: ^6.1.1 permission_handler: ^11.0.0 - url_launcher: ^6.1.6 - package_info_plus: ^8.0.2 + url_launcher: ^6.3.1 + package_info_plus: ^8.1.1 vector_math: ^2.1.4 form_builder_validators: ^11.0.0 #crypto hashlib_codecs: ^2.2.0 - hashlib: ^1.21.0 + hashlib: ^1.21.1 dev_dependencies: flutter_test: sdk: flutter mockito: ^5.3.2 - flutter_lints: ^4.0.0 - riverpod_lint: ^2.6.2 + flutter_lints: ^5.0.0 + riverpod_lint: ^2.6.3 custom_lint: ^0.7.0 - build_runner: ^2.4.9 + build_runner: ^2.4.13 freezed: ^2.5.2 - json_serializable: ^6.4.1 - riverpod_generator: ^2.6.2 - hive_ce_generator: ^1.6.0 + json_serializable: ^6.9.0 + riverpod_generator: ^2.6.3 + hive_ce_generator: ^1.8.0 flutter: fonts: diff --git a/pubspec.yaml b/pubspec.yaml index 99147a437..c184b2633 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,8 @@ environment: dependency_overrides: web: ^0.5.1 # required because network info is using 0.3, but this package is only used by web which we dont build - # used by appinio_video_player, this fixes an issue with gradle + http_parser: ^4.0.2 # Required because dio_smart_retry is in conflict with the flutter sdk otherwise + dependencies: flutter: sdk: flutter @@ -122,7 +123,7 @@ dependencies: gap: ^3.0.1 persistent_header_adaptive: ^2.1.0 extended_wrap: ^0.1.5 - overflow_view: ^0.3.1 + overflow_view: ^0.4.0 flutter_staggered_grid_view: ^0.7.0 auto_size_text: ^3.0.0 snap_scroll_physics: ^0.0.1+3 @@ -147,7 +148,7 @@ dependencies: webview_flutter: ^4.4.0 fl_chart: ^0.69.0 code_text_field: ^1.0.2 - mobile_scanner: ^5.0.1 + mobile_scanner: ^6.0.2 touchable: # path: ../touchable git: @@ -169,18 +170,18 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - build_runner: ^2.4.9 - flutter_lints: ^4.0.0 + build_runner: ^2.4.13 + flutter_lints: ^5.0.0 freezed: ^2.5.2 mockito: ^5.3.2 - json_serializable: ^6.4.1 - riverpod_generator: ^2.6.2 + json_serializable: ^6.9.0 + riverpod_generator: ^2.6.3 # riverpod_lint makes it easier to work with Riverpod - riverpod_lint: ^2.6.2 + riverpod_lint: ^2.6.3 # import custom_lint too as riverpod_lint depends on it custom_lint: ^0.7.0 flutter_native_splash: ^2.2.19 - flutter_launcher_icons: ^0.13.1 + flutter_launcher_icons: ^0.14.1 dart_code_metrics_presets: ^2.5.0 From b66f292ab0b297d02cab2b64e51d1a5d5e210708 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 23 Nov 2024 15:02:52 +0100 Subject: [PATCH 37/64] build: Fix IOS build --- ios/Podfile | 2 +- ios/Runner.xcodeproj/project.pbxproj | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ios/Podfile b/ios/Podfile index 2c89436f2..2ec0bca73 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '13.6' +platform :ios, '15.5' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index dcb7dd753..f8fa6f706 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -635,7 +635,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.6; + IPHONEOS_DEPLOYMENT_TARGET = 15.5; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -662,7 +662,7 @@ INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Mobileraker; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; - IPHONEOS_DEPLOYMENT_TARGET = 13.6; + IPHONEOS_DEPLOYMENT_TARGET = 15.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -724,7 +724,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.6; + IPHONEOS_DEPLOYMENT_TARGET = 15.5; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -773,7 +773,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.6; + IPHONEOS_DEPLOYMENT_TARGET = 15.5; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -802,7 +802,7 @@ INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Mobileraker; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; - IPHONEOS_DEPLOYMENT_TARGET = 13.6; + IPHONEOS_DEPLOYMENT_TARGET = 15.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -836,7 +836,7 @@ INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Mobileraker; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; - IPHONEOS_DEPLOYMENT_TARGET = 13.6; + IPHONEOS_DEPLOYMENT_TARGET = 15.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -991,7 +991,7 @@ INFOPLIST_FILE = MobilerakerNotificationExtention/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = MobilerakerNotificationExtention; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 13.6; + IPHONEOS_DEPLOYMENT_TARGET = 15.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1036,7 +1036,7 @@ INFOPLIST_FILE = MobilerakerNotificationExtention/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = MobilerakerNotificationExtention; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 13.6; + IPHONEOS_DEPLOYMENT_TARGET = 15.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1078,7 +1078,7 @@ INFOPLIST_FILE = MobilerakerNotificationExtention/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = MobilerakerNotificationExtention; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 13.6; + IPHONEOS_DEPLOYMENT_TARGET = 15.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", From f895bf29510f11250ae8176cf009c4f2105d9cbc Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 23 Nov 2024 15:04:25 +0100 Subject: [PATCH 38/64] doc: Update changelog --- docs/changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 23e2550b7..35d7b7ba4 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ ## [2.8.3] - 2024-11-xx +### General + +- **IOS Support**: Due to libraries used in the app, the app now requires IOS 15.5 or newer to run. + ### Enhancements - **Macro Visibility**: Added configurable printer state visibility for macros. Users can now hide macros irrelevant to From fc192e849003696550fa06677a302eb941ef904b Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 23 Nov 2024 15:06:00 +0100 Subject: [PATCH 39/64] New Crowdin updates (#431) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (German) * New translations en.yaml (French) * New translations en.yaml (Hungarian) * New translations en.yaml (Turkish) * New translations en.yaml (Polish) * New translations en.yaml (Hungarian) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Simplified) --- assets/translations/fr.yaml | 1 - assets/translations/hu.yaml | 3 +- assets/translations/pl.yaml | 71 ++ assets/translations/tr.yaml | 1 - assets/translations/zh-CN.yaml | 2 +- assets/translations/zh.yaml | 1327 ++++++++++++++++++++++++++++++++ 6 files changed, 1401 insertions(+), 4 deletions(-) create mode 100644 assets/translations/zh.yaml diff --git a/assets/translations/fr.yaml b/assets/translations/fr.yaml index 78d336e0c..ab3da0186 100644 --- a/assets/translations/fr.yaml +++ b/assets/translations/fr.yaml @@ -647,7 +647,6 @@ dialogs: value: Valeur de l'en-tête value_hint: La valeur de l'en-tête macro_settings: - show_while_printing: Afficher lors de l'impression visible: Visible extruder_feedrate: title: Vitesse de l'extrudeur [mm/s] diff --git a/assets/translations/hu.yaml b/assets/translations/hu.yaml index 576272aca..e16ac9015 100644 --- a/assets/translations/hu.yaml +++ b/assets/translations/hu.yaml @@ -1053,7 +1053,8 @@ dialogs: value: Fejléc-érték value_hint: A fejléc értéke macro_settings: - show_while_printing: Megjelenítés nyomtatáskor + show_for_states: Nyomtatás állapotok + show_for_states_hint: Válaszd ki azokat az állapotokat, amelyekre a makrót meg kell jeleníteni visible: Látható extruder_feedrate: title: Extruder sebesség [mm/s] diff --git a/assets/translations/pl.yaml b/assets/translations/pl.yaml index 425fd3b7a..25c70c329 100644 --- a/assets/translations/pl.yaml +++ b/assets/translations/pl.yaml @@ -3,6 +3,27 @@ general: pause: Pauza resume: Wznów connected: Połączono + 'off': 'Wyłącz' + 'on': 'Włącz' + none: Nic + add: Dodaj + create: Utwórz + rename: Zmień + remove: Usuń + set: Ustaw + use: Użyj + restart: Restartuj + shutdown: Wyłącz + firmware: Firmware + fetching: Pobieranie danych + loading: Ładowanie + unknown: Nieznany + disabled: Wyłączony + confirm: Potwierdź + cancel: Anuluj + close: Zamknij + edit: Edytuj + preview: Podgląd retry: Ponów supported: Wspierane unsupported: Niewspierane @@ -34,12 +55,25 @@ general: archived: Zarchiwizowano leave: Opuść export: Eksport + import: Importuj + current: Aktualny + load: Ładuj + unload: Wyładuj discard: Pomiń hide: Ukryj + finish: Zakończ + select: Wybierz pages: dashboard: + title: Panel + ems_btn: Awaryjne zatrzymanie ems_confirmation: + title: Potwierdź awaryjne zatrzymanie + body: Zamierzasz wysłać polecenie zatrzymania awaryjnego do drukarki. Spowoduje to natychmiastowe zatrzymanie wszystkich silników i grzałek. confirm: STOP! + server_status: + unavailable: Serwer niedostępny + available: Status serwera {} oraz Klippy {} general: print_card: reset: Reset @@ -47,19 +81,36 @@ pages: printing_for: 'Drukowanie: {}' speed: Prędkość layer: Warstwa + eta: Pozostało current_object: Aktualny obiekt elapsed: Upłynęło + flow: Flow filament: Filament + eta_tooltip: |- + Pozostały czas: + Średni: + Slicer: + Plik: + Filament: remaining: Pozostało + print_time: Czas druku cam_card: webcam: Kamera fullscreen: Pełny ekran temp_card: title: Kontrola temperatury + presets_btn: Nastawy + hotend: Głowica grzewcza bed: Łoże + temp_presets: Ustawienia temperatury sensors: Sensory btn_thermistor: Sensor move_card: + title: Przesuń oś + home_xy_tooltip: Pozycja startowa X i Y + home_z_tooltip: Pozycja startowa Z + home_all_tooltip: Pozycjonuj wszystkie osie + home_all_btn: Wszystko qgl_tooltip: Uruchom QGL qgl_btn: QGL mesh_tooltip: Uruchom kalibrację mesh @@ -68,6 +119,26 @@ pages: m84_btn: M84 step_size: Wielkość kroku stc_tooltip: Uruchom pomiar Screws-Tilt + poff_tooltip: + poff_btn: Kalibracja Probe + zoff_btn: Kalibracja krańcówki Z + save_tooltip: Zapisz rezultat kalibracji + save_btn: Zapisz konfigurację + control: + extrude_card: + retract: Retrakcja + macro_card: + no_macros: W tej grupie nie widać obecnie żadnych makr + show_all_tooltip: Pokaż wszystkie makra + pin_card: + title_misc: Różne + filament_sensor: + detected: Wykryto + not_detected: Pusty + multipl_card: + title: Mnożnik + press_adv: Kalibracja ciśnienia PA + smooth_time: Czas wygładzania bottom_sheets: non_printing: manage_service: diff --git a/assets/translations/tr.yaml b/assets/translations/tr.yaml index 01aab297c..d3d68c7ec 100644 --- a/assets/translations/tr.yaml +++ b/assets/translations/tr.yaml @@ -563,7 +563,6 @@ dialogs: value: Başlık Değeri value_hint: Başlığın değeri macro_settings: - show_while_printing: Yazdırırken göster visible: Görünür bottom_sheets: job_queue_sheet: diff --git a/assets/translations/zh-CN.yaml b/assets/translations/zh-CN.yaml index 800c86457..f1bc49d4d 100644 --- a/assets/translations/zh-CN.yaml +++ b/assets/translations/zh-CN.yaml @@ -232,7 +232,7 @@ pages: speed_z: 默认 Z 轴移动速度 speed_z_short: 速度z steps_move: 挤出头移动距离预设值 - steps_move_short: "@:pages.printer_edit.motion_system.steps_move" + steps_move_short: "@:pages.printer_edit.motion_system.steps_move" steps_baby: Z轴偏移量 steps_baby_short: Z Offset extruders: diff --git a/assets/translations/zh.yaml b/assets/translations/zh.yaml new file mode 100644 index 000000000..1f01b02dc --- /dev/null +++ b/assets/translations/zh.yaml @@ -0,0 +1,1327 @@ +--- +general: + pause: 暫停 + resume: 恢復 + connected: 已連線 + 'off': '關閉' + 'on': '開啟' + none: 無 + add: 新增 + create: 建立 + rename: 重新命名 + remove: 刪除 + set: 設定 + use: 使用 + restart: 重新啟動 + shutdown: 關機 + firmware: 韌體 + fetching: 擷取 + loading: 讀取中 + unknown: 未知 + disabled: 停用 + confirm: 確認 + cancel: 取消 + close: 關閉 + edit: 編輯 + preview: 預覽 + retry: 重試 + supported: 支援 + unsupported: 未支援 + details: 詳細資料 + google_play: GooglePlay + ios_store: AppStore + active: 啟用 + canceled: 已取消 + monthly: 每月 + restore: 還原 + accept: 接受 + abort: 忽略 + offset: 偏移 + valid: 有效 + invalid: 無效 + free: 免費 + one_time: 一次性 + delete: 刪除 + clear: 清除 + unlink: 未連結 + save: 儲存 + apply: 套用 + completed: 已完成 + activate: 啟用 + stop: 停止 + start: 開始 + repeat: 重複 + load_more: 載入更多 + archived: 已封存 + leave: 離開 + export: 匯出 + import: 匯出 + current: 目前 + load: 載入 + unload: 卸載 + discard: 放棄 + hide: 隱藏 + finish: 完成 + select: 選擇 +pages: + dashboard: + title: 儀表板 + ems_btn: 緊急停止 + ems_confirmation: + title: 確認緊急停止 + body: 您將向印表機發送緊急停止指令。這將立刻停止所有馬達與加熱器。 + confirm: 停止! + server_status: + unavailable: 沒有可用的伺服器 + available: 伺服器狀態為 {} 且Klippy為 {} + fetching_printer: "@:general.fetching 列印機..." + general: + print_card: + reset: 重設 + reprint: 重新列印 + printing_for: '列印中: {}' + speed: 速度 + layer: 層數 + eta: 預計完成時間 + current_object: 目前物件 + elapsed: 經過的 + flow: 流量 + filament: 線材 + filament_tooltip: 已使用 {} % 的線材,相當於 {} m,總預測長度為 {} m。 + eta_tooltip: |- + 剩餘時間: + 平均: {avg} + 切片軟體::{slicer} + 檔案:{file} + 線材:{filament} + remaining: 剩餘 + print_time: 列印時間 + cam_card: + webcam: 攝影機 + fullscreen: 全螢幕 + error_connecting: |- + 連接至 {} 錯誤。請檢查連線設定並確認攝像頭已連線。 + temp_card: + title: 溫度控制 + presets_btn: 預設集 + hotend: 熱端 + bed: 床面 + temp_presets: 溫度預設集 + sensors: 感測器 + heater_on: "目標 {} °C" + btn_thermistor: 感測器 + temp_preset_card: + cooloff: 關閉冷卻 + h_temp: "@:pages.dashboard.general.temp_card.hotend {}°C" + b_temp: "@:pages.dashboard.general.temp_card.bed {}°C" + move_card: + title: 移動軸 + home_xy_tooltip: 歸位X軸和Y軸 + home_z_tooltip: 歸位Z軸 + home_all_tooltip: 歸位所有軸 + home_all_btn: 全部 + qgl_tooltip: 執行龍門調平 + qgl_btn: 龍門調平 + mesh_tooltip: 執行中間網格校準 + mesh_btn: 網格 + m84_tooltip: 停用馬達 + m84_btn: M84 + step_size: 步進尺寸 + stc_tooltip: 執行螺絲-傾斜計算 + stc_btn: STC + ztilt_tooltip: 執行Z-傾斜計算 + ztilt_btn: z傾斜 + poff_tooltip: 執行探針偏移校準 + poff_btn: 探針校準 + zoff_tooltip: 執行Z終點偏移校準 + zoff_btn: Z終點校準 + bsa_tooltip: 執行床面螺絲調整校準 + bsa_btn: 床面螺絲調整 + save_tooltip: 儲存校準結果 + save_btn: 儲存設定 + more_btn: 更多 + homed: 已歸位的軸 + baby_step_card: + title: 微調Z軸 + z_offset: Z偏移 + restart_klipper: 重啟Klipper + restart_mcu: "@:general.restart @:general.firmware" + control: + fan_card: + title: + zero: 風扇 + one: 風扇 + other: 風扇 + part_fan: 列印件冷卻風扇 + static_fan_btn: 風扇 + extrude_card: + title: 擠出機 + extrude: 擠出 + retract: 回抽 + extrude_len: "@:pages.dashboard.control.extrude_card.extrude length" + cold_extrude_error: 擠出頭尚未達到最低擠出溫度( {}°C ) + macro_card: + title: Gcode-巨集 + no_macros: 目前在此群組中沒有可見的巨集 + add_grp_hint: 您可建立不同群組來編組您的巨集。只需至列印機設定中並新增群組。 + show_all_tooltip: 顯示所有巨集 + pin_card: + title_misc: 雜項 + pin_btn: Pin + filament_sensor: + detected: 監測中 + not_detected: 空白 + power_card: + title: 電源面板 + pin_btn: Pin + provider_error_title: 獲取電源 Api 設備時發生錯誤 + multipl_card: + title: 倍率調整 + flow: 擠出流量 + press_adv: 壓力提前 + smooth_time: 平滑時間 + limit_card: + title: 限制 + velocity: 速度 + accel: 加速度 + sq_corn_vel: 轉角速度 + accel_to_decel: 加速到減速 + fw_retraction_card: + title: 韌體回抽 + retract_length: 回抽長度 + retract_speed: 回抽速度 + extra_unretract_length: 回抽補償長度 + unretract_speed: 回抽補償速度 + bed_mesh_card: + title: 床面網格 + profiles: 設定檔 + range_tooltip: 最高和最低點之間的範圍 + showing_matrix: + probed: 顯示探測矩陣 + mesh: 顯示網格矩陣 + spoolman_card: + title: Spoolman + no_spool: |- + 未選擇料盤。 + 將不追蹤線材用量。 + select_spool: 選擇料盤 + used: '已使用:{}' + provider_error_title: 獲取Spoolman資料時發生錯誤 + gcode_preview_card: + title: GCode預覽 + follow: 追蹤進度 + kinematic_not_supported: 目前app不支援此印表機的運動學系統的 GCode 渲染。 + start_preview: + btn: 開始預覽 + hint: App將下載目前的列印檔案並嘗試進行渲染。 + customizing_dashboard: + title: 個性化儀表板 + cancel_confirm: + title: 放棄變更? + body: |- + 您確定要離開佈局編輯模式嗎? + 所有更改將會遺失。若要儲存您的更改,請按下儲存按鈕。 + add_card: 增加卡片 + remove_page: 移除頁面 + saved_snack: + title: 佈局已儲存! + body: 您的變更已經儲存。 + cant_remove_snack: + title: 無法移除頁面! + body: 您至少需要兩個頁面才能刪除其中一頁。 + confirm_removal: + title: 移除頁面? + body: 您確定真的要移除此頁面? + editing_card: + title: 編輯模式 + body: 現在您可重新排序、新增和刪除卡片及頁面。所有卡片顯示預覽資料。 + error_save_snack: + title: 儲存佈局時發生錯誤! + body: 嘗試儲存佈局時發生錯誤。 + error_no_components: + title: 佈局為空白! + body: 您尚未新增任何小工具。至少新增一個小工具以儲存佈局。 + files: + title: 檔案 + empty_folder: + title: 此資料夾為空白 + subtitle: 未找到檔案 + sort_by: + sort_by: 排序以 + name: 名稱 + last_modified: 上次修改 + last_printed: 上次列印 + file_size: 尺寸 + file_actions: + download: 下載 + delete: 刪除 + copy: 複製 + move: 移動 + rename: 重新命名 + create_file: 建立檔案 + create_folder: 建立資料夾 + upload: 上傳檔案 + upload_bulk: 上傳多個檔案 + zip_file: 建立壓縮檔 + gcode_file_actions: + submit: 送出列印工作 + preheat: 預熱 + enqueue: 加入至列印佇列 + preview: 預覽 + file_operation: + download_canceled: + title: 取消下載 + body: 下載已取消 + download_failed: + title: 下載失敗 + body: 嘗試下載此檔案時發生錯誤,請稍後重試。 + upload_canceled: + title: 取消上傳 + body: 已取消上傳 + upload_success: + title: 上傳成功 + body: 檔案已上傳成功 + upload_failed: + title: 上傳失敗 + body: 嘗試上傳檔案時發生錯誤,請稍後重試。 + zipping_success: + title: 壓縮成功 + body: 已成功建立壓縮檔。 + zipping_failed: + title: 壓縮失敗 + body: 嘗試建立壓縮檔案時發生錯誤,請稍後重試。 + copy_created: + title: 已建立複製檔案 + body: 複製已成功建立於 '{}'。 + move_success: + title: 移動成功 + body: 檔案已成功移動至 '{}'。 + move_failed: + title: 移動失敗 + body: 嘗試移動檔案時發生錯誤。請稍後再試。 + search_files: 在資料夾中搜尋 + search: + clear_search: 清除搜尋紀錄 + waiting: 等待搜尋! + no_results: + title: 未找到檔案 + subtitle: 嘗試使用不同的搜尋詞 + cancel_fab: + upload: 取消上傳 + download: 取消下載 + move_here: 移動到這裡 + copy_here: 複製到這裡 + no_matches_file_pattern: 只允許使用英文字母、數字、底線、破折號和點! + gcode_tab: GCode + config_tab: 設定檔 + timelapse_tab: 縮時攝影 + element: + one: 元素 + other: 元素 + details: + preheat: 預熱 + print: 列印 + general_card: + path: 路徑 + last_mod: 上次修改 + last_printed: 上次列印 + no_data: 沒有資料 + meta_card: + title: GCode 中繼資料 + filament: 線材 + filament_type: 類型 + filament_name: 名稱 + filament_weight: 重量 + filament_length: 長度 + est_print_time: 預計列印時間 + slicer: 使用的切片軟體 + nozzle_diameter: 噴嘴直徑 + layer_higher: 層高 + first_layer: 首層 + others: 其他 + first_layer_temps: "@:pages.files.details.meta_card.first_layer-溫度" + first_layer_temps_value: |- + @:pages.dashboard.general.temp_card.hotend: {}°C + @:pages.dashboard.general.temp_card.bed: {}°C + stat_card: + title: 統計 + preheat_dialog: + title: "@:pages.files.details.preheat ?" + body: |- + 目標溫度 + 擠出機: {}°C + 熱床: {}°C + preheat_snackbar: + title: 開始預熱 + body: |- + 擠出機:{}°C + 床面:{}°C + spoolman_warnings: + insufficient_filament_title: 線材不足 + insufficient_filament_body: |- + 正在使用中的線盤只剩下 {} 線材,這對列印此檔案來說是不足的。點選以變更要使用的線盤。 + material_mismatch_title: 材料不相符 + material_mismatch_body: |- + 此檔案使用材料 {} 與使用中的線盤材料 {} 不相符,點選來更換要使用的線材。 + setting: + title: App - 設定 + general: + title: 一般 + ems_confirm: 確認緊急停止 + ems_confirm_hint: 在傳送緊急停止命令前顯示確認對話框 + always_baby: 始終顯示Z偏移卡片頁面 + always_baby_hint: 始終在儀表板上顯示Z偏移卡片頁面 + num_edit: 數字輸入模式 + num_edit_hint: 使用數字鍵盤輸入數值,而非滑桿 + start_with_overview: 將 @:pages.overview.title 設定為起始頁面 + start_with_overview_hint: 啟動app時顯示總覽頁面,而非啟動列印機的儀表板 + use_offset_pos: 位置偏移 + use_offset_pos_hint: 增加一個偏移到所顯示的座標軸 + lcFullCam: 全螢幕攝影機畫面 + lcFullCam_hint: 在全螢幕模式下將攝影機切換為橫向模式。 + language: 語言 + companion: "想接收遠端通知嗎?\n了解如何在其設備上設定 Mobileraker 的Companion " + sliders_grouping: 編組滑桿卡片 + sliders_grouping_hint: 編組所有滑桿在一張卡片中 + time_format: 時間格式 + system_theme: 介面主題 + system_theme_mode: 主題亮度 + printer_theme_warning: 您目前正在使用與列印機相關的主題。若要修改,請前往列印機的設定。 + filament_sensor_dialog: 顯示線材感測器警告 + filament_sensor_dialog_hint: 當線材感測器被觸發時顯示對話框 + confirm_gcode: 巨集執行確認 + confirm_gcode_hint: 在儀表板上執行 GCode 巨集前,始終顯示確認對話框 + eta_sources: 預計完成時間計算 + eta_sources_hint: 選擇應用於預計完成時間計算的來源 + medium_ui: 平板介面 + medium_ui_hint: 在較大螢幕或橫屏模式下,切換是否使用平板介面 + notification: + title: 通知 + progress_label: 列印進度通知 + progress_helper: 列印進度通知的更新間隔 + state_label: 列印狀態通知 + state_helper: 選擇在進行中的列印工作中觸發通知的狀態 + no_permission_title: 沒有通知權限! + no_permission_desc: |- + 無法顯示通知。 + 請點擊以請求權限! + no_firebase_title: 無法顯示通知 + no_firebase_desc: |- + Mobileraker 無法將通知傳送到您的裝置。 + 看起來您的裝置缺少 Google Play 服務或阻擋了 Firebase 連線! + ios_notifications_title: IOS通知傳送 + ios_notifications_desc: |- + 通知需要至少打開一次 Mobileraker 並保持在背景運行。 + 不過某些通知可能仍會被操作系統阻擋! + missing_companion_title: 未找到Companion! + missing_companion_body: |- + 看起來 Companion 並未安裝,因此以下機器無法發送通知:{} + + 點擊了解更多! + enable_live_activity: 啟用即時活動 + enable_live_activity_helper: 啟用即時活動,並且除了預設的進度通知外,還能使用即時活動功能。 + opt_out_marketing: 行銷通知 + opt_out_marketing_helper: 接收有關銷售和促銷活動的更新 + use_progressbar_notification: 啟用進度條通知 + use_progressbar_notification_helper: 啟用進度條通知,並且除了預設的文字型進度通知外,還能使用進度條通知。 + developer: + title: 開發者 + crashlytics: 啟用 Crashlytics 報告 + imprint: 隱私權政策 / 法律聲明 + printer_edit: + title: 編輯 {} + import_settings: 匯入設定 + remove_printer: 移除列印機 + no_values_found: 未找到任何值! + fetching_additional_settings: "0" + could_not_fetch_additional: 無法獲取附加設置! + fetch_error_hint: 請確保機器是可連接的,並且 Mobileraker 已成功連接到它。 + reset_notification_registry: 清除通知裝置註冊 + configure_remote_connection: 配置遠端連線 + store_error: + title: 儲存失敗! + message: |- + 某些欄位包含無效的值! + 請確認所有欄位的值都是有效的。 + unexpected_error: 儲存機器資料時發生了未預期的錯誤! + confirm_deletion: + title: 刪除 {}? + body: "您即將移除連接於 '{}' 的列印機 '{}'。\n\n請確認您的操作。" + general: + displayname: 顯示名稱 + printer_addr: 列印機-位址 + ws_addr: WebSocket - 位址 + moonraker_api_key: Moonraker-API 金鑰 + moonraker_api_desc: 僅在使用受信任的使用端時需要。FluiddPI 強制執行此設定! + full_url: 完整URL + timeout_label: 使用端逾時設定 + timeout_helper: 使用端連線的逾時秒數 + theme: 介面主題 + theme_helper: 列印機的介面主題 + theme_unavailable: 列印機的介面主題,僅限支持者使用 + ssl: + title: SSL-設定 + pin_certificate_label: 憑證綁定 + pin_certificate_helper: 選擇一個 PEM 格式的憑證檔案以進行 SSL 綁定 + self_signed: 信任自簽名憑證 + motion_system: + title: 運動系統 + invert_x: 反轉X軸 + invert_x_short: 反轉X + invert_y: 反轉Y軸 + invert_y_short: 反轉Y + invert_z: 反轉Z軸 + invert_z_short: 反轉Z + speed_xy: X/Y軸速度 + speed_xy_short: X/Y速度 + speed_z: Z軸速度 + speed_z_short: Z速度 + steps_move: 移動距離 + steps_move_short: "@:pages.printer_edit.motion_system.steps_move" + steps_baby: Z偏移距離 + steps_baby_short: Z距離 + extruders: + title: 擠出機 + feedrate: 擠出機速度 + feedrate_short: 速度 + steps_extrude: 擠出長度 + steps_extrude_short: "@:pages.printer_edit.extruders.steps_extrude" + filament: + loading_distance: 擠出機噴嘴距離 + loading_distance_helper: 從噴嘴到擠出機的距離,用於裝載或卸載線材 + loading_speed: "(卸載)裝載速度" + loading_speed_helper: 從噴嘴到擠出機的線材裝載和卸載速度 + purge_amount: 清理長度 + purge_amount_helper: 清理的線材長度 + purge_speed: 清理速度 + purge_speed_helper: 清理線材的速度 + cams: + target_fps: 目標幀率 + new_cam: 新增網路攝影機 + no_webcams: 未加入網絡攝影機! + stream_url: 串流URL + snapshot_url: 快照URL + default_url: 預設URL + flip_vertical: 垂直翻轉 + flip_horizontal: 水平翻轉 + cam_mode: 攝影模式 + cam_rotate: 旋轉 + read_only: 網路攝影機為唯讀 + macros: + default_grp: 預設 + new_macro_grp: 新增巨集-群組 + no_macros_available: 沒有可用的巨集! + no_macros_found: 未找到巨集! + no_macros_in_grp: 此群組中沒有巨集! + deleted_grp: 刪除群組 {} + macros: 巨集 + default_name: 新增巨集群組 + macros_to_default: + one: 已移動一個巨集至預設群組! + two: 已將兩個巨集移至預設群組! + other: 已將 {} 個巨集移至預設群組! + macro_removed: 找不到該巨集,將會在稍後自動刪除。 + presets: + no_presets: 未加入預設集! + hotend_temp: "@:pages.dashboard.general.temp_card.hotend 溫度" + bed_temp: "@:pages.dashboard.general.temp_card.bed 溫度" + new_preset: 新增預設集 + confirm_fcm_reset: + title: 清除通知設備註冊嗎? + body: "您即將重置設備註冊,該註冊用於確定Companion 應用程式將通知發送到哪些設備。 要重新建立推送通知,您需要在所有設備上重新啟動應用程式並再次將它們連接到機器。" + confirm_remote_interface_removal: + title: + oe: 解除連結 {}? + other: 刪除連線? + body: + oe: 請確認解除將列印機 {} 與 OctoEverywhere 的連結。 + other: 請確認移除列印機 {} 的遠端連線。 + button: + oe: "@:general.unlink" + other: "@:general.remove" + remote_interface_exists: + title: 發現遠端連線 + body: + oe: 這台列印機已經與 OctoEverywhere 連結以進行遠端存取。請先解除連結後再繼續。 + obico: 這台列印機已經與 Obico 連結以進行遠端存取。請先解除連結後再繼續。 + other: 此列印機已經建立遠端連線。請在繼續之前先移除該連線。 + remote_interface_removed: + title: 遠端連線已移除! + body: 請確保儲存列表機設置以套用變更。 + remote_interface_added: + title: + oe: 連線至OctoEverywhere。 + obico: 連線至Obico。 + other: 遠端連線已增加。 + body: 請確認儲存列表機設置以套用變更。 + wifi_access_warning: + title: WiFi資訊不可用 + subtitle: |- + 要確保Mobileraker是否應使用遠程連接,請授予位置存取權限。這樣app可以獲取目前WiFi網路的名稱。 + + 點擊以授予權限。 + local_ssid: + section_header: 智慧切換 + no_ssids: 未加入 WiFi 名稱! + helper: 智慧切換使app根據目前的 WiFi 網路自動在本地和遠端連接之間切換。要啟用此功能,請將您家中的 WiFi 名稱加入到列表中。 + dialog: + title_add: 將 WiFi 名稱加入到列表 + title_edit: 編輯WiFi名稱 + label: WiFi名稱 (SSID) + quick_add_hint: '提示:要快速加入目前的 WiFi 名稱,長按打開此對話框的按鈕。' + error_fetching_snackbar: + title: 獲取 WiFi 名稱時發生錯誤! + body: 請確保app已獲得必要的權限來存取裝置的 WiFi 狀態。 + temp_ordering: + title: 溫度感測器介面排序 + helper: 更改儀表板上溫度感測器的順序。 + no_sensors: 未找到溫度感測器! + fan_ordering: + title: 風扇介面排序 + helper: 變更儀表板上風扇的排列順序 + no_fans: 未找到風扇! + misc_ordering: + title: 雜項元素界面排序 + helper: 更改儀表板上雜項元素的順序。 + no_controls: 未找到雜項元素! + printer_add: + steps: + mode: 模式 + input: 輸入 + test: 測試 + done: 完成 + title: 新增列印機 + initial_name: 我的列印機 + select_mode: + title: '選擇輸入模式:' + body: 如果您是新手,建議使用簡易模式。然而如果您有經驗並需要使用自訂標頭、憑證等,則可以選擇進階模式。請記住,您選擇的模式不會影響應用程式本身,但它會決定如何驗證和顯示輸入,以及在加入機器時可用的選項。 + simple: 簡易 + advanced: 進階 + add_via_oe: 一鍵設定OctoEverywhere + add_via_obico: 一鍵設定Obico + simple_form: + hint_title: 提示-簡易模式 + hint_body: 簡易模式允許您輸入主機和連接埠。如果您需要自訂路徑、標頭、憑證或其他設置,請切換到進階模式。 + url_hint: 列印機的IP或主機名稱 + advanced_form: + hint_title: 提示-進階模式 + hint_body: 請注意,在進階模式下,大多數驗證已被停用。請確保您輸入的 URL 是有效的。 + http_helper: 請輸入 Moonraker 的 HTTP 端點 + ws_helper: 可選的,Moonraker 的 WebSocket 端點 + section_security: 安全性 + section_headers: HTTP-標頭 + empty_headers: 未加入標頭! + test_connection: + section_connection: 連線資料 + section_test: 連線測試 + http_label: Http端點-測試 + ws_label: Webscoket端點-測試 + awaiting: 等待結果... + continue: 繼續 + continue_anyway: 仍然繼續 + proceed_warning: "看起來應用程式無法連接到機器。這可能是因為您與機器不在同一個網路上,或是您沒有必要的權限來存取它。儘管您可以選擇繼續加入機器,但請注意無法保證它會正確連接。請小心操作。\n\n\n\n\n\n\n" + button: 測試連線 + confirmed: + title: 列印機 {} 已加入! + to_dashboard: 到儀表板 + console: + title: 控制台 + card_title: GCode控制台 + no_entries: 找不到緩存的命令 + macro_suggestions: G-Code建議 + no_suggestions: 找不到建議 + command_input: + hint: 輸入控制台命令 + provider_error: + title: 獲取控制台資料時發生錯誤 + body: 嘗試獲取控制台資料時發生錯誤,請稍後再試! + overview: + title: 總覽 + fetching_machines: "@:general.fetching 機器…" + no_entries: 未找到機器 + add_machine: 加入機器 + markdown: + loading: "@:general.loading {}…" + error: 嘗試獲取 {} 時發生錯誤; + open_in_browser: 在瀏覽器中開啟 @:pages.faq.title + faq: + title: FAQ + changelog: + title: 變更紀錄 + paywall: + manage_view: + title: 感謝您的支持! + list_title: '更改支持者等級:' + store_btn: 取消訂閱於{} + sub_warning: 請注意,購買終身支持者等級並不會自動取消任何其他啟用的訂閱。您需要手動取消任何現有的訂閱。然而,即使在購買終身支持者等級後,您仍然可以選擇繼續以定期方式支持Mobileraker的開發。 + subscribe_view: + title: 成為Mobileraker支持者! + info: |- + Mobileraker 的目標是提供一個快速且可靠的 Klipper 行動裝置介面,並秉持 RepRap 精神,推動開源軟體與硬體對社會產生積極影響。 + 由於 Mobileraker 由單一開發者開發並免費提供,因此依賴社群資金來支付運營和開發成本。 + list_title: '選擇支持者等級:' + subscription_info: 如果您選擇訂閱,您可以隨時取消訂閱。訂閱將在目前期間結束前自動續訂,除非您提前取消。要取消訂閱,請前往 {} 並從那裡取消訂閱。 + supporter_tier_list: + error_title: 載入支持者等級時發生錯誤! + error_body: 抱歉,發生了意外錯誤。無法載入支持者等級。請稍後再試! + contact_dialog: + title: 聯繫開發者 + body: |- + Mail: {} + Discord: {} + title: 支持開發者! + calling_store: 處理請求中... + promo_title: 促銷方案 + intro_phase: 享受 {} {} 的折扣 + iap_offer: "原價的 {} 折扣" + tip_developer: 打賞開發者 + trial_disclaimer: 在免費試用結束之前,您不會被收費,並且您可以隨時取消訂閱。 + restore_sign_in: 恢復/登入 + video_player: + downloading_for_sharing: 正在下載影片以供分享… ({}) + tool: + title: 工具 + beltTuner: + title: 皮帶調整器 + description: 確保皮帶張力適當對於3D列印機的最佳性能至關重要。不正確的張力,無論是過緊還是過鬆,都可能導致機械故障、過早磨損以及影響列印質量。提供的張力值僅作為參考點;然而,建議查閱製造商的建議,以獲取針對您的列印機型號的具體指南。調整應基於這些建議以及個別列印機的需求和狀況進行。 + beltType: '選擇您的皮帶類型:' + target: '目標:{} Hz於 {} mm' + permissionWarning: + title: 需要麥克風權限 + subtitle: |- + 皮帶調整器使用您的裝置麥克風來分析皮帶的頻率。 + + 點擊授予權限。 + spoolman: + title: Spoolman + not_available: "Spoolman 在這台列印機上不可用。\n請確保 Spoolman 已經安裝並啟用在您的列印機上。" + learn_more: 若要了解更多關於 Spoolman 及如何安裝,請造訪該專案的 + learn_more_link: GitHub頁面。 + no_spools: 未找到線盤! + no_filaments: 未找到線材! + no_vendors: 未找到製造商! + error_loading_spools: 載入線盤時發生錯誤 + error_loading_filaments: 載入線材時發生錯誤 + spoolman_actions: + activate: "@:general.activate" + deactivate: 未使用 + clone: 建立副本 + edit: "@:general.edit" + archive: 封存 + unarchive: 解除封存 + adjust: 調整線材量 + share_qr: 分享QR碼 + delete: "@:general.delete" + add_spool: 加入線盤 + add_filament: 加入線材 + create: + success: + title: "{} 已建立!" + message: + one: 已成功建立 {}。 + other: 已成功建立 {}。 + error: + title: 建立 {} 時發生錯誤! + message: 未預期的錯誤,請稍後再試。 + update: + success: + title: "已更新 {}!" + message: 已成功更新 {}。 + error: + title: 更新 {} 時發生錯誤! + message: 未預期的錯誤,請稍後再試。 + no_changes: + title: 沒有進行任何變更! + message: 未對 {} 進行任何變更。 + delete: + confirm: + title: 要刪除 {} 嗎? + body: |- + 您即將刪除 {}。 + 此操作無法撤銷。 + + 請確認您的操作。 + success: + title: "已刪除 {} !" + message: + one: 已成功刪除 {}。 + other: 已成功刪除 {}。 + error: + title: 刪除 {} 時發生錯誤! + message: 未預期錯誤,請稍後再試。 + spool: + one: 線盤 + other: 線盤 + filament: + one: 線材 + other: 線材 + vendor: + one: 製造商 + other: 製造商 + properties: + id: ID + name: 名稱 + registered: 登記於 + comment: 備註 + material: 材料 + price: 價格 + density: 密度 + diameter: 線徑 + weight: 重量 + spool_weight: 線盤重量 + article_number: 商品條碼 + first_used: 首次使用 + last_used: 上次使用 + remaining_weight: 剩餘重量 + used_weight: 已用重量 + remaining_length: 剩餘長度 + used_length: 已用長度 + location: 位置 + lot_number: 批號 + color: 顏色 + property_sections: + basic: 基本資訊 + usage: 用量細節 + additional: 附加資訊 + physical: 實體屬性 + print_settings: 列印設定 + vendor_details: + page_title: 製造商 {} + info_card: 製造商資訊 + filaments_card: 根據製造商的線材 + spools_card: 根據製造商的線盤 + filament_details: + info_card: 線材資訊 + spools_card: 線材線盤 + spool_details: + page_title: 線盤 {} + info_card: 線盤資訊 + set_active: 將其設為目前使用的線盤 + archived_warning: + title: 線盤已封存 + body: 此線盤已封存,無法用於新的列印。 + alternative_spool: + same_filament: 替代線盤(相同線材) + same_material: 替代線盤(相同製造商) + spool_form: + create_page_title: 建立線盤 + update_page_title: 編輯線盤 + helper: + price: "@:pages.spoolman.filament_form.helper.price 如果未設定,則預設為線材的價格。" + initial_weight: "@:pages.spoolman.filament_form.helper.initial_weight 如果未設定,則預設為線材的重量。" + empty_weight: "@:pages.spoolman.filament_form.helper.empty_weight 如果未設定,則預設為線材或製造商的線捲重量。" + used_weight: 已使用的線材重量。若為 0 克,則表示該線盤尚未使用。 + location: 您存放線盤的位置 + lot_number: 製造商的批次號碼。可用於確保使用多個線盤時,列印顏色的一致性。 + filament_form: + create_page_title: 建立線材 + update_page_title: 編輯線材 + helper: + price: 整捲線材的價格。 + initial_weight: 這是整捲線材的淨重,排除線盤的重量。通常會標示在包裝上。 + empty_weight: 空線盤的重量。 + vendor_form: + create_page_title: 建立製造商 + update_page_title: 編輯製造商 + helper: + empty_weight: 此製造商空線盤的重量。 +components: + app_version_display: + version: '版本:' + installed_version: '已安裝版本:' + pull_to_refresh: + pull_up_idle: 向下拉以重新整理 + nav_drawer: + printer_settings: 列印機設定 + manage_printers: 管理列印機 + fetching_printers: "@:general.fetching 列印機…" + footer: |- + 由 Patrick Schmidt 用 ❤️ 製作 + 查看該專案的 + connection_watcher: + reconnect: 重新連接 + trying_connect: 嘗試重新連接... + trying_connect_remote: 嘗試透過遠端重新連接... + server_starting: 伺服器正在啟動... + more_details: 更多詳細資料 + add_printer: |- + 您好, + 很高興見到您! + 為了開始使用,請將一台列印機新增到 Mobileraker。完成這一步後,您就能直接在 Mobileraker 內控制你的列印機了。 + octo_indicator: + tooltip: 使用OctoEveryWhere! + supporter_add: + title: 喜歡Mobileraker嗎? + subtitle: 點擊我!了解如何支持開發! + supporter_only_feature: + dialog_title: 僅限支持者功能 + button: 成為Mobileraker支持者 + webcam: 抱歉,類型為 {} 的攝影機僅對支持者可用。作為替代,Mjpeg 攝影機對所有使用者開放。 + printer_add: 您已達到機器上限。只有 Mobileraker 支持者才能加入超過 {} 台機器。 + job_queue: 抱歉,工作佇列僅對支持者開放。 + timelaps_share: 抱歉,縮時攝影分享僅對支持者開放。 + printer_theme: 抱歉,列印機特有主題僅對支持者開放。 + spoolman_page: 抱歉,Spoolman頁面僅對支持者開放。 + custom_dashboard: 抱歉,自訂儀表板功能僅對支持者開放。 + full_file_management: 抱歉,完整檔案管理功能(下載、上傳...) 僅對支持者開放。 + gcode_preview: 抱歉,GCode預覽功能僅對支持者開放。 + machine_deletion_warning: + title: 刪除機器 + subtitle: 看起來您不是支持者。只有支持者才能擁有超過 {} 台機器。您還有 {} 天,超過數量的機器將會被刪除。 + remote_connection_indicator: + title: 使用遠端連線! + web_rtc: + renderer_missing: WebRtc影像不可用! + oe_warning: 尚不支援透過OctoEverywhere 使用WebRtc! + ri_indicator: + tooltip: 使用遠端連線! + obico_indicator: + tooltip: 使用Obico通道! + gcode_preview: + layer: + one: 層數 + other: 層 + move: + one: 移動 + other: 移動 + downloading: + starting: 等待下載以開始... + progress: 下載Gcode檔案中({})... + parsing: + setting_up: 正在設定 GCode 解析器... + progress: 解析Gcode中 ({})... + canceled: Gcode解析已取消! + error: + title: Gcode解析時發生錯誤! + body: 嘗試解析 GCode 時發生錯誤。請重試。如果問題持續存在,請在 GitHub 上提交問題並附上 GCode 檔案。 + error: + config: + title: 獲取列印機配置時發生錯誤! + body: 嘗試獲取列印機配置時發生錯誤。請確保列印機可連接,並且 Mobileraker 已經連接到列印機。 + gcode_preview_settings_sheet: + title: 可視化設置 + show_grid: + title: 顯示格點 + subtitle: 顯示參考格點 + show_axes: + title: 顯示軸 + subtitle: 顯示X/Y軸 + show_next_layer: + title: 顯示下一層 + subtitle: 在預覽中顯示即將到來的層 + show_previous_layer: + title: 顯示前一層 + subtitle: 顯示先前已列印的層 + extrusion_width_multiplier: + prefix: 線寬調整 + show_extrusion: + title: 顯示擠出路徑 + subtitle: 亮顯材料擠出移動 + show_retraction: + title: 顯示回抽路徑 + subtitle: 亮顯線材回抽移動 + show_travel: + title: 顯示空打移動 + subtitle: 顯示空打的移動路徑 + select_color_sheet: + title: 設定顏色 +dialogs: + rate_my_app: + title: 評價Mobileraker? + message: 如果您喜歡 Mobileraker 並希望支持開發,請考慮給 Mobileraker 評分! + import_setting: + fetching: 獲取來源... + fetching_error_title: 無法獲取設定 + fetching_error_sub: 確保其他機器已連接。 + select_source: 選擇來源 + create_folder: + title: 建立資料夾 + label: 資料夾名稱 + delete_folder: + title: 您確定嗎? + description: |- + 該資料夾中的所有檔案也將被刪除! + + 確認刪除資料夾 '/{}'。 + rename_folder: + title: 重新命名資料夾 + label: 資料夾名稱 + copy_folder: + title: 複製資料夾 + label: 資料夾名稱 + delete_file: + description: 確認刪除檔案 '{}'。 + rename_file: + title: 重新命名檔案 + label: 檔案名稱 + copy_file: + title: 複製檔案 + label: 檔案名稱 + create_file: + title: 建立檔案 + label: 檔案名稱 + create_archive: + title: 建立壓縮檔 + label: 壓縮檔名稱 + delete_files: + description: 確認刪除檔案 '{}'。 + exclude_object: + title: 從列印中排除物件 + label: 要排除的物件 + confirm_tile_title: 您確定嗎? + confirm_tile_subtitle: 此操作無法還原! + exclude: 排除 + no_visualization: 沒有可用的視覺化資料! + ws_input_help: + title: URL 輸入幫助 + body: |- + 您可以輸入 IP 地址、URL 或完整的 URI 來指向您的網頁介面,或直接指向由 Moonraker 提供的 WebSocket 實例。 + + 有效範例: + gcode_params: + hint: '提示:如果您想送出具有預設值的巨集,可以長按!' + confirm_title: 執行 {}? + confirm_body: |- + 您即將執行巨集“{}”。 + + 請確認您的操作。 + rgbw: + recent_colors: 最近使用的顏色 + select_machine: + title: 選擇機器 + active_machine: '使用中的機器:{}' + hint: 點擊一台機器將其設為啟用。 + supporter_perks: + title: 支持者福利 + body: 通過支持 Mobileraker,您幫助確保該應用能夠免費供社群使用。此外,支持者還可以獲得以下福利。 + hint: '提示:目前,福利是基於裝置的。未來可能會有所變更。' + theme_perk: + title: 支持者主題 + subtitle: 獨家主題,使用 Material 3 設計風格 + contact_perk: + title: 開發者聯繫 + subtitle: 輕鬆聯繫開發者! + notification_perk: + title: 快照通知 + subtitle: 狀態通知包含攝影機快照 + webrtc_perk: + title: 支援WebRtc + subtitle: 允許使用 WebRtc 攝影機! + job_queue_perk: + title: 工作佇列 + subtitle: 在 Mobileraker 中管理 Moonraker 的工作佇列! + unlimited_printers_perk: + title: 無限制的列印機數量 + subtitle: 使用 Mobileraker 控制您想要的任意數量的 3D列印機! + printer_theme_perk: + title: 列印機基本主題 + subtitle: 為每台列印機選擇不同的主題! + custom_dashboard_perk: + title: 自訂儀表板 + subtitle: 自訂儀表板以符合您的需求! + full_file_management_perk: + title: 完整檔案管理 + subtitle: 解鎖 Mobileraker 的完整檔案管理功能! + bed_screw_adjust: + title: 床台螺絲調整 + xy_plane: '列印機X/Y平面:' + active_screw_title: '使用中的調整螺絲:' + accept_screw_title: '已接受的調整螺絲:' + hint: 如果需要對目前螺絲進行重大調整,請點擊「調整」;否則,請點擊「接受」以繼續。 + adjusted_btn: 已調整 + manual_offset: + title: "@:general.offset 校準" + hint_tooltip: Klipper的紙張測試文件 + snackbar_title: 校準完成 + snackbar_message: 確保通過「儲存配置」操作來永久化偏移量。 + tipping: + title: 打賞開發者! + body: 這個打賞僅僅是用來感謝開發者的努力,並不會授予額外的功能。 + amount_label: 打賞金額 + tip: 打賞 + http_header: + title: Http標頭 + header: 標頭 + header_hint: HTTP標頭名稱 + value: 標頭值 + value_hint: 標頭的值 + macro_settings: + show_for_states: 列印機狀態 + show_for_states_hint: 選擇應顯示巨集的狀態 + visible: 可見 + extruder_feedrate: + title: 擠出機速度 [mm/s] + fan_speed: + title: 編輯 {} % + filament_sensor_triggered: + title: 線材感測器已觸發 + body: |- + 感測器 {} 已被觸發。 + screws_tilt_adjust: + title: 螺絲傾斜調整 + hint: "這個對話框幫助您調整床台的螺絲。對於每個螺絲,您將看到需要的調整量,格式為 HH\n,表示時鐘的方向。例如,01:15 表示旋轉一圈再加上一個四分之一圈,按照圖示指示的方向調整。" + dashboard_page_settings: + title: 儀表板頁面設定 + icon_label: '為此頁面選擇一個圖示:' + filament_switch: + title: + load: 線材裝載精靈 + unload: 線材卸載精靈 + controls: + heat_up: 加熱中 + change_temp: 變更溫度 + purge: 清除 + repeat_load: 重複裝載 + repeat_unload: 重複卸載 + repeat_purge: 重複清除 + steps: + set_temps: + title: 線材溫度 + subtitle: 選擇目標溫度 + heat_up: + title: 工具頭加熱中 + subtitle: 正在加熱至目標溫度 + move: + title: + load: 裝載線材 + unload: 卸載線材 + subtitle: + load: + idle: 將線材插入擠出機 + processing: 裝載線材中... + processed: 已經到達噴嘴嗎?如果需要,請重複操作。 + unload: + idle: 將線材移出擠出機 + processing: 卸載線材中... + processed: 是否已從擠出機中退出線材?如果需要,請重複操作。 + purge: + title: 清理線材 + subtitle: + idle: 裝載線材至噴嘴中 + processing: 清理線材中... + processed: 確認清理過的線材是否乾淨。如有需要,請重複操作。 + heater_temperature: + title: "{} 溫度 [°C]" + confirm_print_cancelation: + title: 取消列印? + body: |- + 您即將取消列印工作。 + + 確認您的操作。 + adjust_spool_filament: + title: 調整線捲線材 + subtitle: 從線盤中增加或減少線材。正值會消耗線材,負值會增加線材。 + input_label: 調整數量 + submit_label: 調整 + tooltip: + weight: 切換至重量模式 + length: 切換至長度模式 + num_range_dialog: + helper: + min: 輸入至少 {} 的數值 + range: 輸入介於 {} 和 {} 之間的數值 +bottom_sheets: + job_queue_sheet: + next: 下個列印工作 + empty: 工作佇列為空。您可以通過檔案瀏覽器加入工作。 + start_queue: 開始佇列 + pause_queue: 暫停佇列 + remove_all: 清除佇列 + remove_all_confirm: 此操作將從佇列中移除所有工作。 + add_remote_con: + disclosure: "{service} 與 Mobileraker 無關。它可能需要額外的訂閱。請查閱 {service} 的網站以獲取更多資訊。" + active_service_info: + title: 發現現有配置檔! + body: 您目前使用的是 {} 作為遠端連線服務。在增加新服務之前,請先將其移除。 + octoeverywehre: + service_name: OctoEverywhere + tab_name: "@:bottom_sheets.add_remote_con.octoeverywehre.service_name" + link: 連結OctoEverywhere + unlink: 取消連結OctoEverywhere + description: OctoEverywhere.com 是一個社群專案,讓您可以從任何地方安全地連接到您的列印機。OctoEverywhere 還提供免費的 AI列印故障檢測、通知、即時串流媒體等功能。設置只需 20 秒鐘! + manual: + service_name: 手動設定遠端連線 + tab_name: 手動 + description: 如果您是進階使用者,您可以選擇手動加入替代連線。當嘗試連線到主要位址失敗時,替代連線將生效。這在您使用反向代理連接到列印機,或擁有可以用來連接列印機的次要 IP 位址時非常有用。 + address_label: 替代位址 + address_hint: 將要使用的遠端連線的基本位址 + obico: + service_name: Obico + tab_name: "@:bottom_sheets.add_remote_con.obico.service_name" + link: 連結Obico + unlink: 取消連結Obico + description: Obico 是一款由社群驅動的完全開源 3D 列印軟體。它讓您可以隨時隨地控制和監控您的 Klipper 列印機,且完全免費。作為 AI 基於失敗檢測的先驅解決方案之一,Obico 僅使用您的列印機網路攝影機進行檢測。 + self_hosted: + title: 自架服務 + description: 預設情況下,Mobileraker 將使用官方的 Obico 實例。然而,如果您正在管理自己的實例,您可以在此指定其 URL。 + url_label: 自架服務URL + url_hint: 自建 Obico 實例的基本 URL + manage_macros_in_grp: + title: 加入巨集 + hint: 選取群組 {} 的巨集 + signIn: + subtitle: 雖然建立帳戶是可選的,但若要在其他設備或平台上恢復購買記錄,則必須建立帳戶。 + forgot_password: 忘記密碼? + forgot_password_success: 密碼重設信件已寄出! + hint: + sign_in: 還沒有帳號嗎? + sign_up: 已有帳號了嗎? + reset_password: 請提供您的email以接收重設密碼的連結。 + action: + sign_in: 登入 + sign_up: 註冊 + reset_password: 重設密碼 + email: + label: Email + hint: 您的Email地址 + password: + label: 密碼 + hint: 您的密碼 + confirm_password: + label: 確認密碼 + hint: 確認您的密碼 + error: 密碼不相符! + profile: + title: 您已登入 + description: 歡迎回來!您的使用者帳號是提升任何行動裝置體驗的關鍵。輕鬆分享您的支持者身份,無論您使用手機、平板或其他裝置,都能輕鬆享受專屬權益。 + restore_purchases: 恢復購買紀錄 + restore_success: 已成功恢復購買紀錄! + sign_out: 登出 + delete_account: 刪除帳號 + email_verification: + title: + pending: 傳送驗證Email + not_verified: 電子郵件未驗證 + description: 請驗證您的電子郵件地址,以確保您始終可以存取您的帳號。 + send: 傳送驗證信 + delete_account_dialog: + title: 刪除帳號? + body: 您即將刪除您的帳號。此操作無法撤銷。 + bedMesh: + no_mesh_loaded: 尚未加載床面網格。 + load_bed_mesh_profile: 讀取床面網格設定檔 + no_mesh: 無網格 + clear_loaded_profile: 清除已載入的設定檔 + cant_render: 目前尚不支援列印機運動學的床面網格渲染。 + select_spool: + header: + spools: 線盤 + qr: QR碼 + no_spools: 未找到線盤。請將線盤加入到 Spoolman + error: '載入線盤時發生錯誤:{}' + qr_loading: 正在載入 QR 掃描器... + qr_error: '載入 QR 掃描器時發生錯誤:{}' + scan_again: 重新掃描 + set_active: 設為使用中 + spool_id_not_found: '抱歉,未找到具有指定 ID #{} 的線盤。' + non_printing: + manage_service: + title: 管理服務 + no_services: 未找到服務! + provider_error: 獲取服務時發生錯誤 + confirm_action: + title: 您確定嗎? + hint: + long_press: '提示:要跳過此操作,長按開啟此確認的按鈕。' + body: + pi_restart: 您即將重新啟動上位機,確定要繼續嗎? + pi_shutdown: 您即將關閉上位機。這將中斷所有啟動中的連線並停止所有正在執行的程序。確定要繼續嗎? + fw_restart: 您即將重新啟動韌體,確定要繼續嗎? + service_start: 您即將啟動服務 '{}'。確定要繼續嗎? + service_restart: 您即將重新啟動服務 '{}'。確定要繼續嗎? + service_stop: 您即將停止服務 '{}'。確定要繼續嗎? + dashboard_cards: + title: 加入卡片 + subtitle: 選擇一張卡片來加入儀表板 + dashboard_layout: + title: 儀表板佈局 + subtitle: '目前佈局:' + available_layouts: + label: '可用的佈局:' + empty: 沒有可用的佈局 + add_empty: 加入空白佈局 + layout_preview: + not_saved: 尚未儲存 + rename_layout: + title: 重新命名佈局 + label: 佈局名稱 + delete_layout: + title: 刪除佈局 + body: |- + 您確定要刪除佈局 {} 嗎? + 所有使用此佈局的機器將恢復為預設佈局。 + import_snackbar: + title: 佈局已匯入! + body: 請確保按下儲存按鈕以套用變更。 + falsy_import_snackbar: + title: 匯入佈局時發生錯誤! + body: 無法匯入佈局。請確保剪貼簿中包含有效的佈局。 + selections: + no_selections: + title: 未找到結果 + subtitle: 嘗試不同的搜尋詞 +klipper_state: + ready: 就緒 + shutdown: 關機 + starting: 啟動中 + disconnected: 已斷開連接 + error: 錯誤 + unauthorized: 未授權 + initializing: 正在初始化 + not_connected: Moonraker 無法與 Klipper 建立連線。請確認 Klipper 是否正在您的系統上運行。 +print_state: + standby: 待機 + printing: 列印中 + paused: 暫停 + complete: 完成 + cancelled: 已取消 + error: 錯誤 +theme_mode: + light: 亮 + dark: 暗 + system: 系統 +notifications: + channel_printer_grp: 列印機 {} + channels: + status: + name: 列印狀態更新-{} + desc: 有關列印狀態的通知。 + title: '{} 的列印狀態已變更!' + body_printing: '開始列印檔案:"{}"' + body_paused: '暫停列印檔案:"{}"' + body_complete: '完成列印檔案:"{}"' + body_error: '列印檔案:"{}" 時發生錯誤' + progress: + name: 列印進度更新-{} + desc: 有關列印進度的通知。 + title: 列印進度:{} +form_validators: + simple_url: '輸入僅允許 URL元件:主機名稱和連接埠。' + disallow_mdns: 不支援 mDNS (.local) 位址 + file_name_in_use: 檔案名稱已經在使用中 +date: + year: + one: 年 + other: 年 + month: + one: 個月 + other: 個月 + week: + one: 週 + other: 週 + day: + one: 日 + other: 日 +date_periods: + year: + one: 每年 + other: 年 + month: + one: 每月 + other: 個月 + week: + one: 每週 + other: 週 + day: + one: 每日 + other: 日 From c3d4a2c4f8448467a7665313ca8294c2fd46a60f Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 23 Nov 2024 15:07:46 +0100 Subject: [PATCH 40/64] doc: Update changelog --- docs/changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 35d7b7ba4..a0f77fe1c 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -27,6 +27,11 @@ - **Obico One-Click Setup**: Corrected an HTTP client-related problem preventing single-click Obico connection setup. +### i18n + +- **Hungarian Translation**: Updated the Hungarian translation, thanks to [@AntoszHUN](https://github.com/AntoszHUN). +- **Chinese Taiwan Translation**: Added a Chinese Taiwan translation, thanks to Kayzed. + ## [2.8.2] - 2024-10-31 ### Bug Fixes From f42249f2aff19bb7585976d1085dc1de042e3fca Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 23 Nov 2024 15:09:26 +0100 Subject: [PATCH 41/64] i18n: Added ZH --- lib/main.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/main.dart b/lib/main.dart index 5dd0725b4..984cc7fbe 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -84,6 +84,7 @@ class MyApp extends ConsumerWidget { Locale('ru'), Locale('tr'), Locale('uk'), + Locale('zh'), Locale('zh', 'CN'), Locale('zh', 'HK'), ], From 03dd769006de2bbe91addd1bb55a217d4d322d45 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 23 Nov 2024 15:32:51 +0100 Subject: [PATCH 42/64] fix: build_runner not working due to collection versioning --- common/pubspec.yaml | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/pubspec.yaml b/common/pubspec.yaml index 489e89215..be6779c12 100644 --- a/common/pubspec.yaml +++ b/common/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependency_overrides: web: ^0.5.1 # required because network info is using 0.3, but this package is only used by web which we dont build - http_parser: ^4.0.2 # Required because dio_smart_retry is in conflict with the flutter sdk otherwise + collection: 1.19.0 # Required because dio_smart_retry is in conflict with the flutter sdk otherwise dependencies: flutter: diff --git a/pubspec.yaml b/pubspec.yaml index c184b2633..5d19e03d6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,7 @@ environment: dependency_overrides: web: ^0.5.1 # required because network info is using 0.3, but this package is only used by web which we dont build - http_parser: ^4.0.2 # Required because dio_smart_retry is in conflict with the flutter sdk otherwise + collection: 1.19.0 # Required because dio_smart_retry is in conflict with the flutter sdk otherwise dependencies: flutter: From f905f59980b1227a0eb8f1dd35295230068bc147 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 23 Nov 2024 21:31:57 +0100 Subject: [PATCH 43/64] fix: XCode archive is an app again and not a lib target --- ios/Podfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ios/Podfile b/ios/Podfile index 2ec0bca73..3b67088d7 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -95,6 +95,10 @@ post_install do |installer| ## dart: PermissionGroup.criticalAlerts # 'PERMISSION_CRITICAL_ALERTS=1' ] + ###### Awesome Notifications pod modification to prevent generic archive! ###### + unless target.name == 'Runner' + config.build_settings['SKIP_INSTALL'] = "YES" + end end end ################ Awesome Notifications pod modification ################### @@ -104,6 +108,10 @@ post_install do |installer| ################ Awesome Notifications pod modification ################### end + + + + ################ Awesome Notifications pod modification ################### awesome_pod_file = File.expand_path(File.join('plugins', 'awesome_notifications', 'ios', 'Scripts', 'AwesomePodFile'), '.symlinks') require awesome_pod_file From 949b61c33a9cb25fa557b7448be052e9f2492fed Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Wed, 27 Nov 2024 21:29:47 +0100 Subject: [PATCH 44/64] feat: Adds day indicator Fix #434 --- .../toolhead_info/toolhead_info_table.dart | 77 +++++++++++++++---- 1 file changed, 62 insertions(+), 15 deletions(-) diff --git a/lib/ui/screens/dashboard/components/toolhead_info/toolhead_info_table.dart b/lib/ui/screens/dashboard/components/toolhead_info/toolhead_info_table.dart index 9ba3a12bc..d2035e88d 100644 --- a/lib/ui/screens/dashboard/components/toolhead_info/toolhead_info_table.dart +++ b/lib/ui/screens/dashboard/components/toolhead_info/toolhead_info_table.dart @@ -14,6 +14,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_icons/flutter_icons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mobileraker/ui/screens/dashboard/components/toolhead_info/toolhead_info_table_controller.dart'; +import 'package:mobileraker/util/extensions/datetime_extension.dart'; import 'package:shimmer/shimmer.dart'; class ToolheadInfoTable extends ConsumerWidget { @@ -145,9 +146,6 @@ class _ToolheadData extends ConsumerWidget { toolheadInfoProvider(machineUUID).selectAs((value) => '${value.currentFlow ?? 0} mm³/s'), ), _ConsumerTooltipCell( - label: tr('pages.dashboard.general.print_card.filament'), - consumerListenable: toolheadInfoProvider(machineUUID) - .selectAs((value) => '${value.usedFilament?.let(numFormatFixed1.format) ?? 0} m'), consumerTooltipListenable: toolheadInfoProvider(machineUUID).selectAs((value) => tr( 'pages.dashboard.general.print_card.filament_tooltip', args: [ @@ -156,13 +154,13 @@ class _ToolheadData extends ConsumerWidget { value.totalFilament?.let(numFormatFixed1.format) ?? '-', ], )), + child: _ConsumerCell( + label: tr('pages.dashboard.general.print_card.filament'), + consumerListenable: toolheadInfoProvider(machineUUID) + .selectAs((value) => '${value.usedFilament?.let(numFormatFixed1.format) ?? 0} m'), + ), ), _ConsumerTooltipCell( - label: tr( - 'pages.dashboard.general.print_card.eta', - ), - consumerListenable: toolheadInfoProvider(machineUUID) - .selectAs((value) => value.eta?.let((eta) => dateFormat.format(eta)) ?? '--:--'), consumerTooltipListenable: toolheadInfoProvider(machineUUID).selectAs((value) => tr( 'pages.dashboard.general.print_card.eta_tooltip', namedArgs: { @@ -172,6 +170,57 @@ class _ToolheadData extends ConsumerWidget { 'filament': value.remainingFilament?.let(secondsToDurationText) ?? '--', }, )), + child: Consumer( + builder: (context, ref, child) { + var asyncValue = ref.watch(toolheadInfoProvider(machineUUID).selectAs((value) { + var eta = value.eta; + + if (eta == null) return ('--:--', null); + var format = dateFormat.format(eta); + int? inDays = null; + if (eta.isNotToday()) { + inDays = eta.difference(DateTime.now()).inDays; + } + return (format, inDays); + })); + + return switch (asyncValue) { + AsyncValue(isLoading: true, isReloading: false) => const _LoadingCell(), + AsyncData(value: (String eta, int? days)) => Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + child!, + AutoSizeText.rich( + TextSpan( + text: eta, + children: [ + if (days != null && days > 0) + TextSpan( + text: '+$days', + style: TextStyle(fontFeatures: [FontFeature.superscripts()]), + ), + ], + ), + maxLines: 1, + stepGranularity: 0.1, + minFontSize: 10, + ), + // Text(asyncValue.requireValue), + ], + ), + ), + _ => const Text('ERR'), + }; + }, + child: AutoSizeText( + tr('pages.dashboard.general.print_card.eta'), + maxLines: 1, + stepGranularity: 0.1, + minFontSize: 10, + ), + ), ), ], ), @@ -262,18 +311,16 @@ class _LoadingCell extends StatelessWidget { class _ConsumerTooltipCell extends StatelessWidget { const _ConsumerTooltipCell({ super.key, - required this.label, - required this.consumerListenable, + required this.child, required this.consumerTooltipListenable, }); - final String label; - final ProviderListenable> consumerListenable; final ProviderListenable> consumerTooltipListenable; + final Widget child; @override Widget build(_) => Consumer( - builder: (context, ref, child) { + builder: (context, ref, innerChild) { var asyncTooltipValue = ref.watch(consumerTooltipListenable); if (asyncTooltipValue.isLoading && !asyncTooltipValue.isReloading) return child!; @@ -282,9 +329,9 @@ class _ConsumerTooltipCell extends StatelessWidget { margin: const EdgeInsets.all(8.0), textAlign: TextAlign.center, message: asyncTooltipValue.requireValue, - child: child!, + child: innerChild!, ); }, - child: _ConsumerCell(label: label, consumerListenable: consumerListenable), + child: child, ); } From 337a41e4db6a7f6d8019c59897060f6dfed4ee08 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Fri, 29 Nov 2024 20:52:40 +0100 Subject: [PATCH 45/64] chore: Improve error handling and meta data handling --- common/lib/service/payment_service.dart | 34 +++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/common/lib/service/payment_service.dart b/common/lib/service/payment_service.dart index e394791e8..64f896972 100644 --- a/common/lib/service/payment_service.dart +++ b/common/lib/service/payment_service.dart @@ -7,8 +7,10 @@ import 'dart:async'; import 'dart:io'; import 'package:common/service/firebase/auth.dart'; +import 'package:common/service/misc_providers.dart'; import 'package:common/util/extensions/object_extension.dart'; import 'package:common/util/logger.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:purchases_flutter/purchases_flutter.dart'; @@ -67,11 +69,13 @@ class CustomerInfoNotifier extends _$CustomerInfoNotifier { @Riverpod(keepAlive: true) bool isSupporter(Ref ref) { + if (kDebugMode) return true; return ref.watch(isSupporterAsyncProvider).valueOrNull == true; } @Riverpod(keepAlive: true) FutureOr isSupporterAsync(Ref ref) async { + if (kDebugMode) return true; var customerInfo = await ref.watch(customerInfoProvider.future); return customerInfo.entitlements.active.containsKey('Supporter') == true; } @@ -130,6 +134,13 @@ class PaymentService { } on PlatformException catch (e) { var errorCode = PurchasesErrorHelper.getErrorCode(e); if (errorCode != PurchasesErrorCode.purchaseCancelledError) { + FirebaseCrashlytics.instance.recordError( + e, + StackTrace.current, + reason: 'Error while trying to purchase', + fatal: true, + ); + logger.e('Error while trying to purchase; $e'); _ref.read(snackBarServiceProvider).show( SnackBarConfig(type: SnackbarType.error, title: 'Unexpected Error', message: errorCode.name.capitalize())); @@ -255,5 +266,28 @@ class PaymentService { }, fireImmediately: true, ); + + _ref.listen( + fcmTokenProvider, + (prev, next) async { + if (!next.hasValue) return; + logger.i('Syncing FCM token with Purchases: ${next.value}'); + await Purchases.setPushToken(next.value!); + logger.i('Synced FCM token with Purchases'); + }, + fireImmediately: true, + ); + + _ref.listen( + versionInfoProvider, + (prev, next) async { + if (!next.hasValue) return; + var packageInfo = next.requireValue; + logger.i('Setting device version to Purchases: ${packageInfo.version}-${packageInfo.buildNumber}'); + await Purchases.setAttributes({r'$deviceVersion': '${packageInfo.version}-${packageInfo.buildNumber}'}); + logger.i('Set device version to Purchases'); + }, + fireImmediately: true, + ); } } From 17053d158e027e97ff4a8f760dc0990359fc1719 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Fri, 29 Nov 2024 21:18:33 +0100 Subject: [PATCH 46/64] chore: Fixed device logs --- common/lib/service/payment_service.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/service/payment_service.dart b/common/lib/service/payment_service.dart index 64f896972..ee691db43 100644 --- a/common/lib/service/payment_service.dart +++ b/common/lib/service/payment_service.dart @@ -283,8 +283,8 @@ class PaymentService { (prev, next) async { if (!next.hasValue) return; var packageInfo = next.requireValue; - logger.i('Setting device version to Purchases: ${packageInfo.version}-${packageInfo.buildNumber}'); - await Purchases.setAttributes({r'$deviceVersion': '${packageInfo.version}-${packageInfo.buildNumber}'}); + logger.i('Setting device version to Purchases: $packageInfo'); + await Purchases.setAttributes({r'$deviceVersion': packageInfo.toString()}); logger.i('Set device version to Purchases'); }, fireImmediately: true, From 2487b46be966fa409f2ed1843eece3b978245452 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Fri, 29 Nov 2024 21:52:43 +0100 Subject: [PATCH 47/64] New Crowdin updates (#433) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) * New translations en.yaml (Chinese Traditional) --- assets/translations/zh.yaml | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/assets/translations/zh.yaml b/assets/translations/zh.yaml index 1f01b02dc..41e5a7ca9 100644 --- a/assets/translations/zh.yaml +++ b/assets/translations/zh.yaml @@ -58,7 +58,7 @@ general: import: 匯出 current: 目前 load: 載入 - unload: 卸載 + unload: 退料 discard: 放棄 hide: 隱藏 finish: 完成 @@ -142,7 +142,7 @@ pages: more_btn: 更多 homed: 已歸位的軸 baby_step_card: - title: 微調Z軸 + title: 微調Z軸距離 z_offset: Z偏移 restart_klipper: 重啟Klipper restart_mcu: "@:general.restart @:general.firmware" @@ -158,8 +158,8 @@ pages: title: 擠出機 extrude: 擠出 retract: 回抽 - extrude_len: "@:pages.dashboard.control.extrude_card.extrude length" - cold_extrude_error: 擠出頭尚未達到最低擠出溫度( {}°C ) + extrude_len: "@:pages.dashboard.control.extrude_card.extrude長度" + cold_extrude_error: 擠出機尚未達到最低擠出溫度( {}°C ) macro_card: title: Gcode-巨集 no_macros: 目前在此群組中沒有可見的巨集 @@ -435,7 +435,7 @@ pages: no_values_found: 未找到任何值! fetching_additional_settings: "0" could_not_fetch_additional: 無法獲取附加設置! - fetch_error_hint: 請確保機器是可連接的,並且 Mobileraker 已成功連接到它。 + fetch_error_hint: 請確保機器是可連接的,並且Mobileraker已成功連接到它。 reset_notification_registry: 清除通知裝置註冊 configure_remote_connection: 配置遠端連線 store_error: @@ -523,8 +523,8 @@ pages: macro_removed: 找不到該巨集,將會在稍後自動刪除。 presets: no_presets: 未加入預設集! - hotend_temp: "@:pages.dashboard.general.temp_card.hotend 溫度" - bed_temp: "@:pages.dashboard.general.temp_card.bed 溫度" + hotend_temp: "@:pages.dashboard.general.temp_card.hotend溫度" + bed_temp: "@:pages.dashboard.general.temp_card.bed溫度" new_preset: 新增預設集 confirm_fcm_reset: title: 清除通知設備註冊嗎? @@ -573,15 +573,15 @@ pages: title: 獲取 WiFi 名稱時發生錯誤! body: 請確保app已獲得必要的權限來存取裝置的 WiFi 狀態。 temp_ordering: - title: 溫度感測器介面排序 + title: 溫度感測器介面順序 helper: 更改儀表板上溫度感測器的順序。 no_sensors: 未找到溫度感測器! fan_ordering: - title: 風扇介面排序 + title: 風扇介面順序 helper: 變更儀表板上風扇的排列順序 no_fans: 未找到風扇! misc_ordering: - title: 雜項元素界面排序 + title: 雜項元素界面順序 helper: 更改儀表板上雜項元素的順序。 no_controls: 未找到雜項元素! printer_add: @@ -598,7 +598,7 @@ pages: simple: 簡易 advanced: 進階 add_via_oe: 一鍵設定OctoEverywhere - add_via_obico: 一鍵設定Obico + add_via_obico: 一鍵設定Obico simple_form: hint_title: 提示-簡易模式 hint_body: 簡易模式允許您輸入主機和連接埠。如果您需要自訂路徑、標頭、憑證或其他設置,請切換到進階模式。 @@ -652,7 +652,7 @@ pages: manage_view: title: 感謝您的支持! list_title: '更改支持者等級:' - store_btn: 取消訂閱於{} + store_btn: 在{} 取消訂閱 sub_warning: 請注意,購買終身支持者等級並不會自動取消任何其他啟用的訂閱。您需要手動取消任何現有的訂閱。然而,即使在購買終身支持者等級後,您仍然可以選擇繼續以定期方式支持Mobileraker的開發。 subscribe_view: title: 成為Mobileraker支持者! @@ -1066,13 +1066,13 @@ dialogs: filament_switch: title: load: 線材裝載精靈 - unload: 線材卸載精靈 + unload: 退料精靈 controls: heat_up: 加熱中 change_temp: 變更溫度 purge: 清除 repeat_load: 重複裝載 - repeat_unload: 重複卸載 + repeat_unload: 重複退料操作 repeat_purge: 重複清除 steps: set_temps: @@ -1084,7 +1084,7 @@ dialogs: move: title: load: 裝載線材 - unload: 卸載線材 + unload: 退料 subtitle: load: idle: 將線材插入擠出機 @@ -1092,7 +1092,7 @@ dialogs: processed: 已經到達噴嘴嗎?如果需要,請重複操作。 unload: idle: 將線材移出擠出機 - processing: 卸載線材中... + processing: 退料中... processed: 是否已從擠出機中退出線材?如果需要,請重複操作。 purge: title: 清理線材 @@ -1289,7 +1289,7 @@ notifications: title: '{} 的列印狀態已變更!' body_printing: '開始列印檔案:"{}"' body_paused: '暫停列印檔案:"{}"' - body_complete: '完成列印檔案:"{}"' + body_complete: '列印完成:"{}"' body_error: '列印檔案:"{}" 時發生錯誤' progress: name: 列印進度更新-{} From 6f68d3f1f5ff378741dd4c46faa715af2a0ccc0f Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 30 Nov 2024 14:56:42 +0100 Subject: [PATCH 48/64] refactor: Adjusted paywall layout a bit --- assets/translations/de.yaml | 1 + assets/translations/en.yaml | 2 ++ lib/ui/screens/paywall/paywall_page.dart | 21 +++++++++++---------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/assets/translations/de.yaml b/assets/translations/de.yaml index ac0c144ad..08c4a670c 100644 --- a/assets/translations/de.yaml +++ b/assets/translations/de.yaml @@ -994,6 +994,7 @@ dialogs: active_machine: 'Aktive Maschine: {}' hint: Tippen Sie auf eine Maschine, um sie zu aktivieren. supporter_perks: + learn_more: Erfahren mehr über die Vorteile title: Supporter Vorteile body: Indem Sie Mobileraker unterstützen, stellen Sie sicher, dass die App für die Community kostenlos bleiben kann. Zusätzlich erhalten Supporter die folgenden Funktionen. hint: 'Hinweis: Zurzeit sind die Vergünstigungen geräteabhängig. Dies könnte sich in Zukunft ändern.' diff --git a/assets/translations/en.yaml b/assets/translations/en.yaml index 697b6261b..9c0827cbf 100644 --- a/assets/translations/en.yaml +++ b/assets/translations/en.yaml @@ -700,6 +700,7 @@ pages: paywall: manage_view: title: Thanks for your Support! + list_title: 'Change Supporter Tier:' store_btn: Cancel Subscription in {} sub_warning: Please take note that purchasing the lifetime supporter tier does @@ -1073,6 +1074,7 @@ dialogs: active_machine: 'Active Machine: {}' hint: Tap on a machine to set it as active. supporter_perks: + learn_more: Learn about Supporter Perks title: Supporter Perks body: By supporting Mobileraker, you ensure the app can stay free for the community. Additionally supporters gain the following list of perks. diff --git a/lib/ui/screens/paywall/paywall_page.dart b/lib/ui/screens/paywall/paywall_page.dart index bd597f774..23923eb7e 100644 --- a/lib/ui/screens/paywall/paywall_page.dart +++ b/lib/ui/screens/paywall/paywall_page.dart @@ -46,7 +46,7 @@ class PaywallPage extends HookConsumerWidget { flexibleSpace: FlexibleSpaceBar( collapseMode: CollapseMode.parallax, background: SvgPicture.asset( - 'assets/vector/undraw_pair_programming_re_or4x.svg', + 'assets/vector/undraw_maker_launch_re_rq81.svg', ), ), ); @@ -310,9 +310,9 @@ class _BenefitOverview extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Learn about Supporter Perks', + 'dialogs.supporter_perks.learn_more', style: Theme.of(context).textTheme.displaySmall?.copyWith(fontSize: 18, fontWeight: FontWeight.bold), - ), + ).tr(), IconButton( onPressed: ref.read(paywallPageControllerProvider.notifier).openPerksInfo, icon: const Icon(Icons.info_outline), @@ -338,11 +338,6 @@ class _ManageTiers extends ConsumerWidget { textAlign: TextAlign.center, style: textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold), ).tr(), - FilledButton.tonalIcon( - icon: const Icon(Icons.contact_support_outlined), - onPressed: ref.read(paywallPageControllerProvider.notifier).openDevContact, - label: const Text('pages.paywall.contact_dialog.title').tr(), - ), const _BenefitOverview(), Align( alignment: Alignment.centerLeft, @@ -363,8 +358,13 @@ class _ManageTiers extends ConsumerWidget { style: textTheme.bodySmall, textAlign: TextAlign.justify, ).tr(), + FilledButton.tonalIcon( + icon: const Icon(Icons.contact_support_outlined), + onPressed: ref.read(paywallPageControllerProvider.notifier).openDevContact, + label: const Text('pages.paywall.contact_dialog.title').tr(), + ), if (model.tipAvailable) const _TippingButton(), - ElevatedButton.icon( + OutlinedButton.icon( icon: const Icon(Icons.subscriptions_outlined), label: const Text('pages.paywall.manage_view.store_btn').tr(args: [storeName()]), onPressed: @@ -393,7 +393,8 @@ class _OfferedProductList extends ConsumerWidget { ); } - logger.e('Got ${packets?.length ?? 0} available Packets: $packets'); + logger.w('Got ${packets?.length ?? 0} available Packets: $packets'); + logger.w('Got ${iapPromos?.length ?? 0} available Promos: $iapPromos'); return Padding( padding: const EdgeInsets.only(bottom: 12.0), From 9e20cf901471334ef95900cbc4189d559b20968e Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 30 Nov 2024 14:56:59 +0100 Subject: [PATCH 49/64] chore: Updated live_activity version --- common/pubspec.yaml | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/pubspec.yaml b/common/pubspec.yaml index be6779c12..fea3d3936 100644 --- a/common/pubspec.yaml +++ b/common/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: awesome_notifications_core: ^0.10.0 awesome_notifications: ^0.10.0 awesome_notifications_fcm: ^0.10.0 - # live_activities: ^1.9.3 + # live_activities: ^2.1.0 live_activities: git: url: https://github.com/Clon1998/flutter_live_activities diff --git a/pubspec.yaml b/pubspec.yaml index 5d19e03d6..8df135d13 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -91,7 +91,7 @@ dependencies: awesome_notifications_core: ^0.10.0 awesome_notifications: ^0.10.0 awesome_notifications_fcm: ^0.10.0 - # live_activities: ^1.9.3 + # live_activities: ^2.1.0 live_activities: # path: ../flutter_live_activities git: From ef16caa667abf34f31f7deb31a6af3997a8c4fab Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 30 Nov 2024 18:23:08 +0100 Subject: [PATCH 50/64] fix: Ensured URI extension correctly handles paths that end with "/" --- common/lib/util/extensions/uri_extension.dart | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/common/lib/util/extensions/uri_extension.dart b/common/lib/util/extensions/uri_extension.dart index a7b03068f..b1cbaaa40 100644 --- a/common/lib/util/extensions/uri_extension.dart +++ b/common/lib/util/extensions/uri_extension.dart @@ -8,10 +8,15 @@ import 'dart:convert'; import 'package:common/util/extensions/string_extension.dart'; extension MobilerakerUri on Uri { - String skipScheme() => (hasScheme) ? toString().replaceRange(0, scheme.length - 1, '') : toString(); + Uri appendPath(String pathToAppend) { + final List adjustedSegments = pathSegments.toList(); + if (adjustedSegments.isNotEmpty && adjustedSegments.last.isEmpty) { + adjustedSegments.removeLast(); + } + adjustedSegments.addAll(pathToAppend.split('/').where((element) => element.isNotEmpty)); - Uri appendPath(String path) => - replace(pathSegments: pathSegments + path.split('/').where((element) => element.isNotEmpty).toList()); + return replace(pathSegments: adjustedSegments); + } Uri removePort() => replace( port: switch (scheme) { From 90e611b7646f914ac3b78dfd8532aa993aee4749 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 30 Nov 2024 18:23:32 +0100 Subject: [PATCH 51/64] test: Increase test coverage uri_extension --- common/test/unit/util/uri_extension_test.dart | 128 +++++++++++++++++- 1 file changed, 127 insertions(+), 1 deletion(-) diff --git a/common/test/unit/util/uri_extension_test.dart b/common/test/unit/util/uri_extension_test.dart index cc865be84..5fbd6082d 100644 --- a/common/test/unit/util/uri_extension_test.dart +++ b/common/test/unit/util/uri_extension_test.dart @@ -3,11 +3,14 @@ * All rights reserved. */ +import 'dart:convert'; + import 'package:common/util/extensions/uri_extension.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - group('UriExtension', () { + // Existing toHttpUri tests (as you provided) + group('toHttpUri', () { test('toHttpUri should return http scheme for http and ws schemes', () { final uri = Uri.parse('ws://example.com'); final result = uri.toHttpUri(); @@ -57,4 +60,127 @@ void main() { expect(result.port, 8080); }); }); + + group('toWebsocketUri', () { + test('toWebsocketUri should convert http to ws', () { + final uri = Uri.parse('http://example.com'); + final result = uri.toWebsocketUri(); + expect(result.scheme, 'ws'); + }); + + test('toWebsocketUri should convert https to wss', () { + final uri = Uri.parse('https://example.com'); + final result = uri.toWebsocketUri(); + expect(result.scheme, 'wss'); + }); + + test('toWebsocketUri should convert ws to ws', () { + final uri = Uri.parse('ws://example.com'); + final result = uri.toWebsocketUri(); + expect(result.scheme, 'ws'); + }); + + test('toWebsocketUri should convert wss to wss', () { + final uri = Uri.parse('wss://example.com'); + final result = uri.toWebsocketUri(); + expect(result.scheme, 'wss'); + }); + + test('toWebsocketUri should convert other schemes to ws', () { + final uri = Uri.parse('ftp://example.com'); + final result = uri.toWebsocketUri(); + expect(result.scheme, 'ws'); + }); + + test('toWebsocketUri should remove standard ports', () { + final uri = Uri.parse('http://example.com:80'); + final result = uri.toWebsocketUri(); + expect(result.port, 0); + + final uri2 = Uri.parse('https://example.com:443'); + final result2 = uri2.toWebsocketUri(); + expect(result2.port, 0); + }); + + test('toWebsocketUri should preserve non-standard ports', () { + final uri = Uri.parse('http://example.com:8080'); + final result = uri.toWebsocketUri(); + expect(result.port, 8080); + }); + }); + + group('appendPath', () { + test('appendPath should add path segments', () { + final uri = Uri.parse('http://example.com'); + final result = uri.appendPath('users/profile'); + expect(result.pathSegments, ['users', 'profile']); + }); + + test('appendPath should handle existing path segments', () { + final uri = Uri.parse('http://example.com/base'); + final result = uri.appendPath('users/profile'); + expect(result.pathSegments, ['base', 'users', 'profile']); + }); + + test('appendPath should handle empty path segments', () { + final uri = Uri.parse('http://example.com'); + final result = uri.appendPath('///users///profile///'); + expect(result.pathSegments, ['users', 'profile']); + }); + + test('appendPath should handle uri with ending slash', () { + final uri = Uri.parse('http://example.com/mystream/'); + final result = uri.appendPath('users'); + expect(result.pathSegments, ['mystream', 'users']); + }); + }); + + group('removePort', () { + test('removePort should set port to 80 for http', () { + final uri = Uri.parse('http://example.com:8080'); + final result = uri.removePort(); + expect(result.port, 80); + }); + + test('removePort should set port to 443 for https', () { + final uri = Uri.parse('https://example.com:8443'); + final result = uri.removePort(); + expect(result.port, 443); + }); + + test('removePort should set port to 0 for other schemes', () { + final uri = Uri.parse('ftp://example.com:21'); + final result = uri.removePort(); + expect(result.port, 0); + }); + }); + + group('removeUserInfo', () { + test('removeUserInfo should remove user information', () { + final uri = Uri.parse('http://user:pass@example.com'); + final result = uri.removeUserInfo(); + expect(result.userInfo, ''); + }); + + test('removeUserInfo should not change uri without user info', () { + final uri = Uri.parse('http://example.com'); + final result = uri.removeUserInfo(); + expect(result.userInfo, ''); + }); + }); + + group('basicAuth', () { + test('basicAuth should return null for empty user info', () { + final uri = Uri.parse('http://example.com'); + final result = uri.basicAuth; + expect(result, null); + }); + + test('basicAuth should return base64 encoded user info', () { + final uri = Uri.parse('http://user:pass@example.com'); + final result = uri.basicAuth; + final expectedAuth = base64Encode(utf8.encode('user:pass')); + expect(result, 'Basic $expectedAuth'); + }); + }); } From 8a8e37eefc96b79c409382f9c55ca4d6b8f2467c Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sat, 30 Nov 2024 18:43:29 +0100 Subject: [PATCH 52/64] fix: Day indicator not working correclty --- .../components/toolhead_info/toolhead_info_table.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ui/screens/dashboard/components/toolhead_info/toolhead_info_table.dart b/lib/ui/screens/dashboard/components/toolhead_info/toolhead_info_table.dart index d2035e88d..7fbcfc7d2 100644 --- a/lib/ui/screens/dashboard/components/toolhead_info/toolhead_info_table.dart +++ b/lib/ui/screens/dashboard/components/toolhead_info/toolhead_info_table.dart @@ -179,7 +179,9 @@ class _ToolheadData extends ConsumerWidget { var format = dateFormat.format(eta); int? inDays = null; if (eta.isNotToday()) { - inDays = eta.difference(DateTime.now()).inDays; + // Add 1 day as the difference requires 24 hours to be a day + // 1.1.2024 23:59 - 2.1.2024 04:00 = 0 days -> still next day -> +1 to show eta at 04:00 + 1 day + inDays = eta.difference(DateTime.now()).inDays + 1; } return (format, inDays); })); From 129b11a2f20d2adf47829da90bdd5f422dbc2474 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 1 Dec 2024 12:07:06 +0100 Subject: [PATCH 53/64] New Crowdin updates (#437) * New translations en.yaml (German) * New translations en.yaml (Hungarian) --- assets/translations/hu.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/translations/hu.yaml b/assets/translations/hu.yaml index e16ac9015..5db315eae 100644 --- a/assets/translations/hu.yaml +++ b/assets/translations/hu.yaml @@ -998,6 +998,7 @@ dialogs: active_machine: 'Aktív gép: {}' hint: Érintsd meg a gépet az aktívként való beállításhoz. supporter_perks: + learn_more: Tudjon meg többet a támogatói kedvezményekről title: Támogatói jutalmak body: A Mobileraker támogatásával biztosítod, hogy az alkalmazás ingyenes maradjon mindenkinek. Ezenkívül a támogatók az alábbi jutalmakat is megkapják. hint: 'Tipp: Jelenleg a jutalmak eszközalapúak. Ez változhat a jövőben.' From c7a1a5e09e2ca5eaeb6a5441d3f759d5f0f4f2f1 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 1 Dec 2024 12:29:30 +0100 Subject: [PATCH 54/64] feat: Add sort by estimated print time --- assets/translations/de.yaml | 1 + assets/translations/en.yaml | 1 + common/lib/data/dto/files/gcode_file.dart | 6 ++++++ common/lib/data/enums/sort_mode_enum.dart | 1 + common/lib/data/model/sort_configuration.dart | 3 +++ common/lib/util/time_util.dart | 4 ++-- .../files/details/gcode_file_details_page.dart | 5 ++--- lib/ui/screens/files/file_manager_page.dart | 11 ++++++++++- 8 files changed, 26 insertions(+), 6 deletions(-) diff --git a/assets/translations/de.yaml b/assets/translations/de.yaml index 08c4a670c..8b8084395 100644 --- a/assets/translations/de.yaml +++ b/assets/translations/de.yaml @@ -252,6 +252,7 @@ pages: last_modified: Zuletzt bearbeitet last_printed: Zuletzt gedruckt file_size: Dateigröße + estimated_time: Geschätzte Druckzeit file_actions: download: Herunterladen delete: Löschen diff --git a/assets/translations/en.yaml b/assets/translations/en.yaml index 9c0827cbf..dcf78952d 100644 --- a/assets/translations/en.yaml +++ b/assets/translations/en.yaml @@ -257,6 +257,7 @@ pages: last_modified: Last modified last_printed: Last printed file_size: Size + estimated_time: Estimated print time file_actions: download: Download delete: Delete diff --git a/common/lib/data/dto/files/gcode_file.dart b/common/lib/data/dto/files/gcode_file.dart index 5fae4de0c..b35611a60 100644 --- a/common/lib/data/dto/files/gcode_file.dart +++ b/common/lib/data/dto/files/gcode_file.dart @@ -67,6 +67,12 @@ class GCodeFile with _$GCodeFile, RemoteFile { return a.printStartTime?.compareTo(b.printStartTime ?? 0) ?? -1; } + static int estimatedPrintTimeComparator(RemoteFile a, RemoteFile b) { + if (a is! GCodeFile || b is! GCodeFile) return 0; + + return a.estimatedTime?.compareTo(b.estimatedTime ?? 0) ?? -1; + } + const GCodeFile._(); @JsonSerializable(fieldRename: FieldRename.snake) diff --git a/common/lib/data/enums/sort_mode_enum.dart b/common/lib/data/enums/sort_mode_enum.dart index 176c9174b..2ee25c271 100644 --- a/common/lib/data/enums/sort_mode_enum.dart +++ b/common/lib/data/enums/sort_mode_enum.dart @@ -10,6 +10,7 @@ enum SortMode { name('pages.files.sort_by.name', SortKind.ascending), lastModified('pages.files.sort_by.last_modified', SortKind.descending), lastPrinted('pages.files.sort_by.last_printed', SortKind.descending), + estimatedPrintTime('pages.files.sort_by.estimated_time', SortKind.descending), size('pages.files.sort_by.file_size', SortKind.ascending); const SortMode(this.translation, this.defaultKind); diff --git a/common/lib/data/model/sort_configuration.dart b/common/lib/data/model/sort_configuration.dart index 990db3d75..84b005c49 100644 --- a/common/lib/data/model/sort_configuration.dart +++ b/common/lib/data/model/sort_configuration.dart @@ -27,6 +27,9 @@ class SortConfiguration { SortMode.lastModified when kind == SortKind.descending => (a, b) => RemoteFile.modifiedComparator(b, a), SortMode.size when kind == SortKind.ascending => RemoteFile.sizeComparator, SortMode.size when kind == SortKind.descending => (a, b) => RemoteFile.sizeComparator(b, a), + SortMode.estimatedPrintTime when kind == SortKind.ascending => GCodeFile.estimatedPrintTimeComparator, + SortMode.estimatedPrintTime when kind == SortKind.descending => (a, b) => + GCodeFile.estimatedPrintTimeComparator(b, a), _ => throw UnimplementedError('Unknown sort mode: $mode'), }; return comp; diff --git a/common/lib/util/time_util.dart b/common/lib/util/time_util.dart index 41a488a50..e2e31603c 100644 --- a/common/lib/util/time_util.dart +++ b/common/lib/util/time_util.dart @@ -5,8 +5,8 @@ import 'package:easy_localization/easy_localization.dart'; -String secondsToDurationText(int sec) { - var d = Duration(seconds: sec); +String secondsToDurationText(num sec) { + var d = Duration(seconds: sec.toInt()); var seconds = d.inSeconds; final days = seconds ~/ Duration.secondsPerDay; seconds -= days * Duration.secondsPerDay; diff --git a/lib/ui/screens/files/details/gcode_file_details_page.dart b/lib/ui/screens/files/details/gcode_file_details_page.dart index 2dc8d8d32..af3c9f44d 100644 --- a/lib/ui/screens/files/details/gcode_file_details_page.dart +++ b/lib/ui/screens/files/details/gcode_file_details_page.dart @@ -21,7 +21,6 @@ import 'package:common/service/ui/bottom_sheet_service_interface.dart'; import 'package:common/service/ui/dialog_service_interface.dart'; import 'package:common/service/ui/snackbar_service_interface.dart'; import 'package:common/ui/components/warning_card.dart'; -import 'package:common/ui/mobileraker_icons.dart'; import 'package:common/util/extensions/async_ext.dart'; import 'package:common/util/extensions/build_context_extension.dart'; import 'package:common/util/extensions/gcode_file_extension.dart'; @@ -240,7 +239,7 @@ class _CompactBody extends HookConsumerWidget { _PropertyTile( title: 'pages.files.details.meta_card.est_print_time'.tr(), subtitle: - '${secondsToDurationText(model.file.estimatedTime?.toInt() ?? 0)}, ${tr('pages.dashboard.general.print_card.eta')}: ${model.file.formatPotentialEta(dateFormatEta)}', + '${secondsToDurationText(model.file.estimatedTime ?? 0)}, ${tr('pages.dashboard.general.print_card.eta')}: ${model.file.formatPotentialEta(dateFormatEta)}', ), _PropertyTile( title: 'pages.files.details.meta_card.slicer'.tr(), @@ -449,7 +448,7 @@ class _MediumBody extends HookConsumerWidget { _PropertyTile( title: 'pages.files.details.meta_card.est_print_time'.tr(), subtitle: - '${secondsToDurationText(model.file.estimatedTime?.toInt() ?? 0)}, ${tr('pages.dashboard.general.print_card.eta')}: ${model.file.formatPotentialEta(dateFormatEta)}', + '${secondsToDurationText(model.file.estimatedTime ?? 0)}, ${tr('pages.dashboard.general.print_card.eta')}: ${model.file.formatPotentialEta(dateFormatEta)}', ), _PropertyTile( title: 'pages.files.details.meta_card.slicer'.tr(), diff --git a/lib/ui/screens/files/file_manager_page.dart b/lib/ui/screens/files/file_manager_page.dart index 47cb3e6ce..024d1d116 100644 --- a/lib/ui/screens/files/file_manager_page.dart +++ b/lib/ui/screens/files/file_manager_page.dart @@ -41,6 +41,7 @@ import 'package:common/util/extensions/number_format_extension.dart'; import 'package:common/util/extensions/object_extension.dart'; import 'package:common/util/extensions/ref_extension.dart'; import 'package:common/util/logger.dart'; +import 'package:common/util/time_util.dart'; import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/foundation.dart'; @@ -948,6 +949,8 @@ class _FileItem extends ConsumerWidget { Widget subtitle = switch (sortMode) { SortMode.size => Text(numberFormat.formatFileSize(file.size)), + SortMode.estimatedPrintTime when file is GCodeFile => + Text((file as GCodeFile).estimatedTime?.let(secondsToDurationText) ?? '--'), SortMode.lastPrinted when file is GCodeFile => Text((file as GCodeFile).lastPrintDate?.let(dateFormat.format) ?? '--'), SortMode.lastPrinted => const Text('--'), @@ -1015,7 +1018,13 @@ class _ModernFileManagerController extends _$ModernFileManagerController { String get _relativeToRoot => filePath.split('/').skip(1).join('/'); List get _availableSortModes => switch (_root) { - 'gcodes' => [SortMode.name, SortMode.lastModified, SortMode.lastPrinted, SortMode.size], + 'gcodes' => [ + SortMode.name, + SortMode.lastModified, + SortMode.lastPrinted, + SortMode.estimatedPrintTime, + SortMode.size + ], _ => [SortMode.name, SortMode.lastModified, SortMode.size], }; From 5e0062aeb7d65664724e4c89e4170e0bda3d1de8 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 1 Dec 2024 12:31:50 +0100 Subject: [PATCH 55/64] doc: Adds eta changes and new sort-by --- docs/changelog.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index a0f77fe1c..659a66a8a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -10,9 +10,12 @@ - **Macro Visibility**: Added configurable printer state visibility for macros. Users can now hide macros irrelevant to the current printer state by tapping the macro in the group editor on the printer edit page. - - **Remote Services Randomization**: Randomized the order of remote services on the printer add and edit pages to ensure a more fair representation. +- **GCode File Sorting**: Added support for sorting GCode files by estimated print time, allowing users to quickly find + the shortest or longest print jobs. +- **Eta Day Indicator**: Enhanced the ETA cell in the status card to display a `+n` to indicate the the eta is in n + days. (e.g. `+1` for tomorrow) ### Bug Fixes From 703e6412320e5c6fc39df4dc78224a2949020880 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 1 Dec 2024 12:40:42 +0100 Subject: [PATCH 56/64] fix: Layout of params dialog overflowing --- .../components/dialog/macro_params/macro_params_dialog.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ui/components/dialog/macro_params/macro_params_dialog.dart b/lib/ui/components/dialog/macro_params/macro_params_dialog.dart index 7e6c9c80d..2f050b776 100644 --- a/lib/ui/components/dialog/macro_params/macro_params_dialog.dart +++ b/lib/ui/components/dialog/macro_params/macro_params_dialog.dart @@ -65,9 +65,9 @@ class _MacroParamsDialog extends ConsumerWidget { mainAxisSize: MainAxisSize.min, // To make the card compact children: [ if (paramNames.isNotEmpty) - _ParamsDialog(macro: macro, paramNames: paramNames) + Flexible(child: _ParamsDialog(macro: macro, paramNames: paramNames)) else - _ConfirmDialog(macro: macro), + Flexible(child: _ConfirmDialog(macro: macro)), const Divider(), Text( 'dialogs.gcode_params.hint', From ae04a6f07df449be0067445baf37626c18dcb794 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 1 Dec 2024 21:38:15 +0100 Subject: [PATCH 57/64] feat: Add polish translation --- lib/main.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/main.dart b/lib/main.dart index 984cc7fbe..1b55c42b8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -79,6 +79,7 @@ class MyApp extends ConsumerWidget { Locale('hu'), Locale('it'), Locale('nl'), + Locale('pl'), Locale('pt', 'BR'), Locale('ro'), Locale('ru'), From 34eb4e43a6d46abe5b6989626cb493c1a074b170 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Mon, 2 Dec 2024 19:14:45 +0100 Subject: [PATCH 58/64] New Crowdin updates (#438) * New translations en.yaml (German) * New translations en.yaml (Hungarian) * New translations en.yaml (Polish) * New translations en.yaml (Polish) * New translations en.yaml (Polish) * New translations en.yaml (Polish) * New translations en.yaml (Polish) --- assets/translations/hu.yaml | 1 + assets/translations/pl.yaml | 201 ++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) diff --git a/assets/translations/hu.yaml b/assets/translations/hu.yaml index 5db315eae..c71013871 100644 --- a/assets/translations/hu.yaml +++ b/assets/translations/hu.yaml @@ -253,6 +253,7 @@ pages: last_modified: Utolsó módosítás last_printed: Utoljára nyomtatott file_size: Méret + estimated_time: Becsült nyomtatási idő file_actions: download: Letöltés delete: Törlés diff --git a/assets/translations/pl.yaml b/assets/translations/pl.yaml index 25c70c329..aa05a2493 100644 --- a/assets/translations/pl.yaml +++ b/assets/translations/pl.yaml @@ -124,6 +124,7 @@ pages: zoff_btn: Kalibracja krańcówki Z save_tooltip: Zapisz rezultat kalibracji save_btn: Zapisz konfigurację + more_btn: Więcej control: extrude_card: retract: Retrakcja @@ -139,7 +140,196 @@ pages: title: Mnożnik press_adv: Kalibracja ciśnienia PA smooth_time: Czas wygładzania + limit_card: + velocity: Prędkość + accel: Akceleracja + fw_retraction_card: + retract_length: Długość retrakcji + retract_speed: Prędkość retrakcji + bed_mesh_card: + title: Siatka stołu + profiles: Profile + gcode_preview_card: + follow: Śledź postęp + start_preview: + btn: Włącz podgląd + customizing_dashboard: + cancel_confirm: + title: Odrzucić zmiany? + add_card: Dodaj kartę + remove_page: Usuń stronę + saved_snack: + title: Układ zapisany! + body: Twoje zmiany zostały zapisane. + confirm_removal: + title: Usunąć stronę? + editing_card: + title: Tryb edycji + error_save_snack: + title: Błąd w zapisie układu! + error_no_components: + title: Pusty układ! + files: + title: Pliki + empty_folder: + title: Ten folder jest pusty + subtitle: Nie znaleziono plików + sort_by: + sort_by: Sortuj według + name: Nazwa + last_modified: Ostatnia modyfikacja + last_printed: Ostatnio drukowany + file_size: Wielkość + estimated_time: Przewidywany czas druku + file_actions: + download: Pobierz + delete: Usuń + copy: Kopiuj + move: Przenieś + rename: Zmień nazwę + create_file: Utwórz plik + create_folder: Utwórz folder + upload: Wyślij plik + upload_bulk: Wyślij pliki + zip_file: Utwórz archiwum + gcode_file_actions: + preheat: Podgrzej + enqueue: Dodaj do kolejki wydruku + preview: Podgląd + file_operation: + download_canceled: + title: Pobieranie anulowane + body: Pobieranie zostało anulowane. + download_failed: + title: Pobieranie nieudane + body: Wystąpił błąd podczas próby pobrania pliku. Spróbuj ponownie później. + upload_canceled: + title: Wysyłanie anulowane + body: Wysyłanie zostało anulowane. + upload_success: + title: Wysyłanie powiodło się + body: Plik został poprawnie wysłany. + upload_failed: + title: Wysyłanie nieudane + copy_created: + title: Kopia utworzona + move_success: + title: Ruch zakończony sukcesem + move_failed: + title: Ruch nieudany + search_files: Szukaj w katalogu + search: + clear_search: Wyczyść wyszukiwanie + no_results: + title: Nie znaleziono plików + cancel_fab: + upload: Anuluj wysyłanie + download: Anuluj pobieranie + move_here: Przenieś tutaj + copy_here: Kopiuj tutaj + details: + print: Drukuj + general_card: + path: Scieżka + meta_card: + filament_name: Nazwa + filament_weight: Waga + filament_length: Długość + est_print_time: Szacowany czas wydruku + slicer: Użyty Slicer + nozzle_diameter: Średnica dyszy + layer_higher: Wysokość warstwy + stat_card: + title: Statystyki + setting: + general: + language: Język + time_format: Format czasu + printer_edit: + import_settings: Importuj ustawienia + remove_printer: Usuń drukarkę + store_error: + title: Zapisywanie nieudane! + message: |- + Niektóre pola zawierają nieprawidłowe wartości! + Upewnij się, że wszystkie pola są prawidłowe. + unexpected_error: Wystąpił nieoczekiwany błąd podczas próby zapisania danych maszyny! + confirm_deletion: + title: Usunąć {}? + body: "Zamierzasz usunąć drukarkę '{}' podłączoną do '{}'.\n\nPotwierdź swoją czynność." + general: + displayname: Wyświetlana nazwa + printer_addr: Drukarka - adres + full_url: '!Wysokość warstwy' + timeout_label: Upłynął czas oczekiwania + theme: Motyw UI + theme_helper: Motyw UI dla drukarki + motion_system: + invert_x_short: Odwróć X + invert_y_short: Odwróć Y + invert_z_short: Odwróć Z + extruders: + feedrate_short: Prędkość + filament: + loading_distance: Odległość dyszy ekstrudera + cams: + target_fps: Decelowa ilość FPS + new_cam: Nowa kamera + no_webcams: Nie dodano kamer! + flip_vertical: Odwróć w pionie + flip_horizontal: Odwróć w poziomie + local_ssid: + no_ssids: Nie dodano nazwy sieci WiFi! + dialog: + title_add: Dodaj nazwę WiFi do listy + title_edit: Edytuj nazwę WiFi + label: Nazwa WiFi (SSID) + printer_add: + steps: + done: Zrobione + test_connection: + button: Przetestuj połączenie + confirmed: + title: Drukarka {} dodana! + console: + title: Konsola + card_title: GCode Konsola + no_suggestions: Nie znaleziono sugestii! + command_input: + hint: Wprowadź polecenie konsoli + overview: + title: Podsumowanie + add_machine: Dodaj maszyę + tool: + title: Narzędzia + beltTuner: + title: Tuning paska + spoolman: + learn_more_link: Strona GitHub. + spoolman_actions: + deactivate: Dezaktywacja + clone: Klonuj + properties: + comment: Komentarz + first_used: Pierwsze użycie + last_used: Ostanie użycie + location: Lokalizacja + color: Kolor + property_sections: + basic: Podstawowe informacje + filament_form: + create_page_title: Utwórz filament + update_page_title: Edytuj filament +components: + app_version_display: + version: 'Wersja:' + installed_version: 'Zainstalowana wersja:' + nav_drawer: + printer_settings: Ustawienia drukarki + fetching_printers: "…………" bottom_sheets: + bedMesh: + no_mesh: Brak siatki stołu non_printing: manage_service: no_services: Nie znaleziono usług! @@ -148,6 +338,17 @@ bottom_sheets: title: Jesteś pewien? hint: long_press: 'Podpowiedź: Aby pominąć, przytrzymaj dłużej przycisk, który otworzył to potwierdzenie.' + dashboard_layout: + layout_preview: + not_saved: Nie zapisano +klipper_state: + shutdown: Zamknięcie +print_state: + printing: Drukowanie + error: Błąd +theme_mode: + light: Jasny + dark: Ciemny notifications: channels: status: From ab1e35f2b3b070c168a21c5e779c5cead278aab4 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Wed, 4 Dec 2024 22:59:48 +0100 Subject: [PATCH 59/64] feat: Adds shortcut for cooling down heaters via long press --- lib/ui/components/graph_card_with_button.dart | 3 +++ .../components/temperature_card/heater_sensor_card.dart | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/ui/components/graph_card_with_button.dart b/lib/ui/components/graph_card_with_button.dart index deea80463..797f13913 100644 --- a/lib/ui/components/graph_card_with_button.dart +++ b/lib/ui/components/graph_card_with_button.dart @@ -18,6 +18,7 @@ class GraphCardWithButton extends StatelessWidget { required this.builder, required this.buttonChild, required this.onTap, + this.onLongPress, }); final Color? backgroundColor; @@ -25,6 +26,7 @@ class GraphCardWithButton extends StatelessWidget { final WidgetBuilder builder; final Widget buttonChild; final VoidCallback? onTap; + final VoidCallback? onLongPress; final List plotSpots; @override @@ -93,6 +95,7 @@ class GraphCardWithButton extends StatelessWidget { disabledForegroundColor: themeData.colorScheme.onPrimary.withOpacity(0.38), ), onPressed: onTap, + onLongPress: onLongPress, child: buttonChild, ), ], diff --git a/lib/ui/screens/dashboard/components/temperature_card/heater_sensor_card.dart b/lib/ui/screens/dashboard/components/temperature_card/heater_sensor_card.dart index 1c1256b01..f3f180057 100644 --- a/lib/ui/screens/dashboard/components/temperature_card/heater_sensor_card.dart +++ b/lib/ui/screens/dashboard/components/temperature_card/heater_sensor_card.dart @@ -245,6 +245,7 @@ class _HeaterMixinTile extends HookConsumerWidget { plotSpots: spots.value, buttonChild: const Text('general.set').tr(), onTap: klippyCanReceiveCommands ? () => controller.adjustHeater(heater) : null, + onLongPress: klippyCanReceiveCommands ? () => controller.turnOffHeater(heater) : null, builder: (BuildContext context) { var innerTheme = Theme.of(context); return Tooltip( @@ -554,7 +555,7 @@ class _Controller extends _$Controller { ); } - adjustHeater(HeaterMixin heater) { + void adjustHeater(HeaterMixin heater) { double? maxValue; var configFile = ref.read(printerProvider(machineUUID).selectRequireValue((value) => value.configFile)); if (heater is Extruder) { @@ -587,7 +588,7 @@ class _Controller extends _$Controller { }); } - editTemperatureFan(TemperatureFan temperatureFan) { + void editTemperatureFan(TemperatureFan temperatureFan) { var configFan = ref .read(printerProvider(machineUUID) .selectAs((value) => value.configFile.fans[(temperatureFan.kind, temperatureFan.configName)])) @@ -612,6 +613,10 @@ class _Controller extends _$Controller { ref.read(printerServiceSelectedProvider).setTemperatureFanTarget(temperatureFan.name, v.toInt()); }); } + + void turnOffHeater(HeaterMixin heater) { + _printerService.setHeaterTemperature(heater.name, 0); + } } class _PreviewController extends _Controller { From 92c56d2fd68ad751ea859b54f75c597064458dcb Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Wed, 4 Dec 2024 23:00:46 +0100 Subject: [PATCH 60/64] doc: Add info about heater Shortcut --- docs/changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 659a66a8a..fa44e3c9e 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -16,6 +16,8 @@ the shortest or longest print jobs. - **Eta Day Indicator**: Enhanced the ETA cell in the status card to display a `+n` to indicate the the eta is in n days. (e.g. `+1` for tomorrow) +- **Heater Shortcut**: Added a convenient long-press gesture on the "Set" button within the heater card to instantly + disable the heater, improving user interaction and control. ### Bug Fixes From 6b65b6ea0bd1ff23c088bf91723c363c6370ae31 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 8 Dec 2024 15:54:04 +0100 Subject: [PATCH 61/64] fix: Color picker sheet using wrong animation --- lib/ui/components/bottomsheet/color_picker_sheet.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ui/components/bottomsheet/color_picker_sheet.dart b/lib/ui/components/bottomsheet/color_picker_sheet.dart index cea4562c5..b8ced979b 100644 --- a/lib/ui/components/bottomsheet/color_picker_sheet.dart +++ b/lib/ui/components/bottomsheet/color_picker_sheet.dart @@ -60,7 +60,7 @@ class ColorPickerSheet extends HookConsumerWidget { style: ElevatedButton.styleFrom( foregroundColor: themeData.colorScheme.primary, backgroundColor: themeData.colorScheme.surface), onPressed: () { - Navigator.pop(context, BottomSheetResult.confirmed(null)); + context.pop(BottomSheetResult.confirmed(null)); }, icon: const Icon(Icons.search_off), tooltip: 'general.clear'.tr(), From 21461303c14a510f474954d482da9f4c91b33f55 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 8 Dec 2024 23:51:11 +0100 Subject: [PATCH 62/64] fix: Filepicker on Android --- assets/translations/de.yaml | 4 +++ assets/translations/en.yaml | 4 +++ .../lib/service/moonraker/file_service.dart | 8 +++--- docs/changelog.md | 3 +++ lib/service/ui/file_interaction_service.dart | 26 +++++++++++++++++-- 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/assets/translations/de.yaml b/assets/translations/de.yaml index 8b8084395..e31b2f220 100644 --- a/assets/translations/de.yaml +++ b/assets/translations/de.yaml @@ -285,6 +285,10 @@ pages: upload_failed: title: Upload fehlgeschlagen body: Beim Hochladen der Datei ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut. + reasons: + type_mismatch: + title: Dateityp nicht unterstützt + body: Nur '{}' Dateien sind erlaubt. zipping_success: title: Archiv erstellt body: Das Archiv wurde erfolgreich erstellt. diff --git a/assets/translations/en.yaml b/assets/translations/en.yaml index dcf78952d..457de3969 100644 --- a/assets/translations/en.yaml +++ b/assets/translations/en.yaml @@ -290,6 +290,10 @@ pages: upload_failed: title: Upload failed body: An error occurred while trying to upload the file. Please retry later. + reasons: + type_mismatch: + title: File type mismatch + body: Only '{}' files are allowed. zipping_success: title: Zipping successful body: The archive was successfully created. diff --git a/common/lib/service/moonraker/file_service.dart b/common/lib/service/moonraker/file_service.dart index e6d0113d9..54b937953 100644 --- a/common/lib/service/moonraker/file_service.dart +++ b/common/lib/service/moonraker/file_service.dart @@ -203,8 +203,8 @@ Future fileApiResponse(Ref ref, String machineUUID, String } @riverpod -Future moonrakerFolderContent(Ref ref, String machineUUID, String path, - SortConfiguration sortConfig) async { +Future moonrakerFolderContent( + Ref ref, String machineUUID, String path, SortConfiguration sortConfig) async { ref.keepAliveFor(); ref.listen(fileNotificationsProvider(machineUUID, path), (prev, next) => next.whenData((d) => ref.invalidateSelf())); // await Future.delayed(const Duration(milliseconds: 5000)); @@ -226,15 +226,13 @@ Future moonrakerFolderContent(Ref ref, String machineUUID, /// 2. https://moonraker.readthedocs.io/en/latest/web_api/#file-list-changed class FileService { FileService(Ref ref, this._machineUUID, this._jRpcClient, this._dio) - : _downloadReceiverPortName = 'downloadFilePort-${_machineUUID.hashCode}', - _apiRequestTimeout = + : _apiRequestTimeout = _jRpcClient.timeout > const Duration(seconds: 30) ? _jRpcClient.timeout : const Duration(seconds: 30) { ref.onDispose(dispose); ref.listen(jrpcMethodEventProvider(_machineUUID, 'notify_filelist_changed'), _onFileListChanged); } final String _machineUUID; - final String _downloadReceiverPortName; final StreamController _fileActionStreamCtrler = StreamController(); diff --git a/docs/changelog.md b/docs/changelog.md index fa44e3c9e..be4f0a284 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -32,6 +32,9 @@ - **Obico One-Click Setup**: Corrected an HTTP client-related problem preventing single-click Obico connection setup. +- **Filepicker on Android**: Fixed an issue that prevented the user from choosing a file to upload on some Android + versions. + ### i18n - **Hungarian Translation**: Updated the Hungarian translation, thanks to [@AntoszHUN](https://github.com/AntoszHUN). diff --git a/lib/service/ui/file_interaction_service.dart b/lib/service/ui/file_interaction_service.dart index a849b3548..b112db862 100644 --- a/lib/service/ui/file_interaction_service.dart +++ b/lib/service/ui/file_interaction_service.dart @@ -3,6 +3,8 @@ * All rights reserved. */ +import 'dart:io'; + import 'package:common/common.dart'; import 'package:common/data/dto/files/folder.dart'; import 'package:common/data/dto/files/gcode_file.dart'; @@ -620,9 +622,11 @@ class FileInteractionService { logger.i('[FileInteractionService($_machineUUID)] uploading file. Allowed: $allowedFileTypes'); + bool useAny = kDebugMode || Platform.isAndroid; + FilePickerResult? result = await FilePicker.platform.pickFiles( - type: kDebugMode ? FileType.any : FileType.custom, - allowedExtensions: allowedFileTypes.unless(kDebugMode), + type: useAny ? FileType.any : FileType.custom, + allowedExtensions: allowedFileTypes.unless(useAny), withReadStream: true, allowMultiple: multiple, withData: false, @@ -630,6 +634,23 @@ class FileInteractionService { logger.i('[FileInteractionService($_machineUUID)] FilePicker result: $result'); if (result == null || result.count == 0) return; + + // If we did not filter by OS, we need to check the file extension manually here + + if (useAny) { + final invalidFiles = result.files.where((e) => !allowedFileTypes.contains(e.extension)); + if (invalidFiles.isNotEmpty) { + _snackBarService.show(SnackBarConfig( + type: SnackbarType.error, + title: tr('pages.files.file_operation.upload_failed.reasons.type_mismatch.title'), + message: tr('pages.files.file_operation.upload_failed.reasons.type_mismatch.body', + args: [allowedFileTypes.map((e) => '.$e').join(', ')]), + )); + yield const FileActionFailed(action: FileSheetAction.uploadFile, files: [], error: 'Invalid file type'); + return; + } + } + for (var toUpload in result.files) { logger.i('[FileInteractionService($_machineUUID)] Selected file: ${toUpload.name}'); @@ -868,6 +889,7 @@ class FileInteractionService { } catch (e, s) { logger.e('[FileInteractionService($_machineUUID)] Could not upload file.', e, s); _onOperationError(e, s, 'upload'); + yield const FileActionFailed(action: FileSheetAction.uploadFile, files: [], error: 'Upload failed'); } } From 9b8abdf70cc2ef68663c1d69ffb1daae730ff2c2 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 8 Dec 2024 23:56:25 +0100 Subject: [PATCH 63/64] doc: Updated i18n info and new languages --- docs/changelog.md | 1 + docs/contribute_i18n.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index be4f0a284..0267352ab 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -39,6 +39,7 @@ - **Hungarian Translation**: Updated the Hungarian translation, thanks to [@AntoszHUN](https://github.com/AntoszHUN). - **Chinese Taiwan Translation**: Added a Chinese Taiwan translation, thanks to Kayzed. +- **Polish Translation**: Added a Polish translation, thanks to Solargrim. ## [2.8.2] - 2024-10-31 diff --git a/docs/contribute_i18n.md b/docs/contribute_i18n.md index d9b975916..157746ce1 100644 --- a/docs/contribute_i18n.md +++ b/docs/contribute_i18n.md @@ -120,12 +120,14 @@ To edit an existing language file manually: - 🇿🇦 Afrikaans, [@DMT07](https://github.com/DMT07) - 🇭🇰 Chinese Hong Kong, [@old-cookie](https://github.com/old-cookie) - 🇨🇳 Chinese Mainland, [@emo64](https://github.com/emo64), [@ptsa](https://github.com/ptsa) +- 🇹🇼 Chinese Taiwan, Kayzed - 🇳🇱 Dutch, [@JSMPI](https://github.com/JSMPI) - 🇬🇧 English, [@Clon1998](https://github.com/Clon1998) - 🇫🇷 French, [@Jothoreptile](https://github.com/Jothoreptile), Arnaud Petetin, [@dtourde](https://github.com/dtourde) - 🇩🇪 German, [@Clon1998](https://github.com/Clon1998) - 🇭🇺 Hungarian, [@AntoszHUN](https://github.com/AntoszHUN) - 🇮🇹 Italian, [@Livex97](https://github.com/Livex97) +- 🇵🇱 Polish, solargrim - 🇧🇷 Portuguese Brasil, [@opastorello](https://github.com/opastorello) - 🇷🇴 Romanian, [@vaxxi](https://github.com/vaxxi) - 🇷🇺 Russian, [@teuchezh](https://github.com/teuchezh) From aaa4cfedd66455337b19de8f9b86fdca8cc80ad3 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Sun, 8 Dec 2024 23:56:47 +0100 Subject: [PATCH 64/64] New Crowdin updates (#439) * New translations en.yaml (Polish) * New translations en.yaml (Polish) * New translations en.yaml (Polish) --- assets/translations/pl.yaml | 172 ++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/assets/translations/pl.yaml b/assets/translations/pl.yaml index aa05a2493..149365c6b 100644 --- a/assets/translations/pl.yaml +++ b/assets/translations/pl.yaml @@ -104,6 +104,7 @@ pages: bed: Łoże temp_presets: Ustawienia temperatury sensors: Sensory + heater_on: "{} °C cel" btn_thermistor: Sensor move_card: title: Przesuń oś @@ -125,11 +126,25 @@ pages: save_tooltip: Zapisz rezultat kalibracji save_btn: Zapisz konfigurację more_btn: Więcej + baby_step_card: + title: Mikro Kalibracja Osi Z + restart_klipper: Zrestartuj Klippera control: + fan_card: + title: + zero: Wentylator + one: Wentylator + other: Wentylatory + part_fan: Wentylator druku + static_fan_btn: Wentylator extrude_card: + title: Ekstruder + extrude: Ekstruzja retract: Retrakcja macro_card: + title: Gcode - Makra no_macros: W tej grupie nie widać obecnie żadnych makr + add_grp_hint: Możesz tworzyć różne grupy, aby organizować swoje makra. Po prostu przejdź do ustawień drukarki i dodaj nową grupę. show_all_tooltip: Pokaż wszystkie makra pin_card: title_misc: Różne @@ -138,9 +153,11 @@ pages: not_detected: Pusty multipl_card: title: Mnożnik + flow: Przepływ press_adv: Kalibracja ciśnienia PA smooth_time: Czas wygładzania limit_card: + title: Limity velocity: Prędkość accel: Akceleracja fw_retraction_card: @@ -149,8 +166,15 @@ pages: bed_mesh_card: title: Siatka stołu profiles: Profile + spoolman_card: + no_spool: |- + Nie wybrano szpuli. + Zużycie filamentu nie będzie śledzone. + used: 'Użyty: {}' gcode_preview_card: + title: GCode Podgląd follow: Śledź postęp + kinematic_not_supported: Obecnie aplikacja nie obsługuje renderowania kodu GCode dla układu kinematycznego tej drukarki. start_preview: btn: Włącz podgląd customizing_dashboard: @@ -227,11 +251,20 @@ pages: download: Anuluj pobieranie move_here: Przenieś tutaj copy_here: Kopiuj tutaj + element: + one: Element + other: Elementy details: + preheat: Podgrzanie print: Drukuj general_card: path: Scieżka + last_mod: Ostatnio modyfikowany + last_printed: Ostatnio drukowany + no_data: Brak danych meta_card: + filament: Filament + filament_type: Typ filament_name: Nazwa filament_weight: Waga filament_length: Długość @@ -239,15 +272,36 @@ pages: slicer: Użyty Slicer nozzle_diameter: Średnica dyszy layer_higher: Wysokość warstwy + first_layer: Pierwsza warstwa + others: Inne stat_card: title: Statystyki + preheat_dialog: + body: |- + Zadane temperatury + Ekstruder: {}°C + Stół: {}°C + preheat_snackbar: + body: |- + Ekstruder: {}°C + Stół: {}°C + spoolman_warnings: + insufficient_filament_title: Niewystarczająca ilość filamentu + material_mismatch_title: Niedopasowanie materiałów setting: + title: Ustawienia aplikacji general: + title: Ogólne + ems_confirm: Potwierdź zatrzymanie awaryjne language: Język time_format: Format czasu + notification: + title: Powiadomienia printer_edit: + title: Edytuj {} import_settings: Importuj ustawienia remove_printer: Usuń drukarkę + reset_notification_registry: Wyczyść rejestr urządzeń powiadamiających store_error: title: Zapisywanie nieudane! message: |- @@ -284,9 +338,17 @@ pages: title_add: Dodaj nazwę WiFi do listy title_edit: Edytuj nazwę WiFi label: Nazwa WiFi (SSID) + fan_ordering: + no_fans: Brak wentylatorów! printer_add: steps: done: Zrobione + title: Dodaj nową drukarkę + initial_name: Moja drukarka + select_mode: + advanced: Zaawansowane + advanced_form: + section_security: Bezpieczeństwo test_connection: button: Przetestuj połączenie confirmed: @@ -309,8 +371,23 @@ pages: spoolman_actions: deactivate: Dezaktywacja clone: Klonuj + update: + success: + title: "{} zaktualizowano!" + error: + title: Błąd aktualizacji {}! + message: Wystąpił nieoczekiwany błąd. Spróbuj ponownie później. + no_changes: + title: Nie wprowadzono żadnych zmian! + delete: + confirm: + title: Usunąć {}? + success: + title: "{} usunięto!" properties: comment: Komentarz + price: Cena + weight: Waga first_used: Pierwsze użycie last_used: Ostanie użycie location: Lokalizacja @@ -326,10 +403,83 @@ components: installed_version: 'Zainstalowana wersja:' nav_drawer: printer_settings: Ustawienia drukarki + manage_printers: Zarządzaj drukarkami fetching_printers: "…………" + connection_watcher: + reconnect: Połącz ponownie + supporter_add: + title: Lubisz Mobileraker? + gcode_preview: + layer: + one: Warstwa + other: Warstwy + move: + one: Ruch + other: Ruchy + gcode_preview_settings_sheet: + show_grid: + title: Pokaż siatkę + select_color_sheet: + title: Wybierz kolor +dialogs: + create_folder: + title: Utwórz folder + macro_settings: + visible: Widoczny + filament_switch: + steps: + move: + title: + load: Ładuj filament + unload: Wyładuj filament + subtitle: + load: + processing: Ładowanie filamentu... + unload: + processing: Wyładowywanie filamentu... + purge: + title: Oczyść filament + subtitle: + processing: Czyszczenie filamentu... + confirm_print_cancelation: + title: Anulować drukowanie? + body: |- + Zamierzasz anulować zadanie drukowania. + + Potwierdź swoją akcję. bottom_sheets: + manage_macros_in_grp: + title: Dodaj makro + signIn: + forgot_password: Zapomniałeś hasła? + action: + sign_in: Zaloguj + sign_up: Wyloguj + reset_password: Zresetuj hasło + email: + label: Email + hint: Twój adres email + password: + label: Hasło + hint: Twoje hasło + confirm_password: + label: Potwierdź hasło + hint: Potwierdź swoje hasło + error: Hasła są niezgodne! + profile: + title: Jesteś zalogowany. + restore_purchases: Przywróć zakupy + restore_success: Zakupy zostały pomyślnie przywrócone! + delete_account: Usuń konto. + delete_account_dialog: + title: Usunąć konto? bedMesh: + load_bed_mesh_profile: Załaduj profil siatki stołu no_mesh: Brak siatki stołu + select_spool: + header: + qr: Kod QR + scan_again: Skanuj ponownie non_printing: manage_service: no_services: Nie znaleziono usług! @@ -341,17 +491,39 @@ bottom_sheets: dashboard_layout: layout_preview: not_saved: Nie zapisano + rename_layout: + label: Nazwa układu + delete_layout: + title: Usuń układ + selections: + no_selections: + title: Nie znaleziono wyników. klipper_state: + ready: Gotowy shutdown: Zamknięcie + starting: Startowanie + disconnected: Rozłączony + error: Błąd + unauthorized: Nieautoryzowany + initializing: Inicjalizacja print_state: + standby: Czuwanie printing: Drukowanie + paused: Pauza + complete: Zakończono + cancelled: Anulowany error: Błąd theme_mode: light: Jasny dark: Ciemny + system: System notifications: + channel_printer_grp: Drukarka {} channels: status: + name: Aktualizacje statusu wydruku - {} + desc: Powiadomienia dotyczące statusu drukowania. + body_printing: 'Drukowanie pliku: "{}"' body_paused: 'Wstrzymano drukowanie pliku: "{}"' body_complete: 'Zakończono druk: "{}"' body_error: 'Błąd podczas druku pliku: "{}"'