diff --git a/android/app/build.gradle b/android/app/build.gradle index e680dd6f1..bb7e6f813 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_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + jvmTarget = '17' } 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/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 94adc3a3f..b5850a089 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,8 @@ -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 +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 8ba491344..74320c0e7 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() @@ -24,12 +23,12 @@ 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.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" +include ":app" \ No newline at end of file diff --git a/assets/translations/de.yaml b/assets/translations/de.yaml index 2d06bb4df..e31b2f220 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 @@ -284,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. @@ -899,33 +904,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: @@ -995,6 +999,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.' @@ -1050,7 +1055,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..457de3969 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 @@ -289,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. @@ -700,6 +705,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 +1079,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. @@ -1131,7 +1138,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/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 e81c91864..c71013871 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ő @@ -251,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 @@ -927,6 +930,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 +971,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 @@ -995,6 +999,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.' @@ -1050,7 +1055,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..149365c6b 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,37 @@ 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 + heater_on: "{} °C cel" 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,7 +120,366 @@ 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ę + 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 + filament_sensor: + detected: Wykryto + 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: + retract_length: Długość retrakcji + retract_speed: Prędkość retrakcji + 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: + 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 + 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ść + est_print_time: Szacowany czas wydruku + 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: |- + 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) + 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: + 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 + 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 + 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 + 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! @@ -77,9 +488,42 @@ 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 + 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: "{}"' 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..41e5a7ca9 --- /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長度" + 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: 日 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/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/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/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/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 06f720adf..4cc77c78e 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,23 @@ 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; + ConfigFileObjectIdentifiers get kind; + + 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/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 6d2d66856..354dcd898 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,21 @@ 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, kind: identifier), + ConfigFileObjectIdentifiers.led || + ConfigFileObjectIdentifiers.pca9533 || + ConfigFileObjectIdentifiers.pca9632 => + DumbLed(name: name, kind: identifier), + _ => 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.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 42afeb18e..7f7e8a09c 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'; @@ -43,41 +35,46 @@ 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, - 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(); @@ -144,20 +141,19 @@ 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) { throw const MobilerakerException('Missing field: toolhead'); } - if (gCodeMove == null) { throw const MobilerakerException('Missing field: gCodeMove'); } @@ -212,19 +208,19 @@ 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; // - // 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; + 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 +246,25 @@ class PrinterBuilder { return builder..configFile = config; } - static PrinterBuilder _updateControllerFan(String fanName, Map fanJson, PrinterBuilder builder) { - final curFan = builder.fans[fanName] ?? ControllerFan(name: fanName); + 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); + final curFan = builder.fans[key] ?? NamedFan.fallback(identifier, name); - if (curFan is! ControllerFan) { - logger.w('Fan "$fanName" is not a ControllerFan'); - throw MobilerakerException('Fan "$fanName" is not a ControllerFan. Found ${_typeOrNull(curFan)}'); - } - - 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); + static PrinterBuilder _updateLed( + ConfigFileObjectIdentifiers identifier, String name, Map json, PrinterBuilder builder) { + final key = (identifier, name); + final curLed = builder.leds[key] ?? Led.fallback(identifier, name); - if (curLed is! AddressableLed) { - logger.w('Led "$led" is not an AddressableLed'); - throw MobilerakerException('Led "$led" is not an AddressableLed. Found ${_typeOrNull(led)}'); - } - - 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 +294,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); + 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 +320,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 +347,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)); @@ -446,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 +} \ No newline at end of file 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/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/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/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/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/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. 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..39283d8dc 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; @@ -735,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/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..54b937953 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())); @@ -204,7 +204,7 @@ Future fileApiResponse(FileApiResponseRef ref, String mach @riverpod Future moonrakerFolderContent( - MoonrakerFolderContentRef ref, String machineUUID, String path, SortConfiguration sortConfig) async { + 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,16 +225,14 @@ 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) - : _downloadReceiverPortName = 'downloadFilePort-${_machineUUID.hashCode}', - _apiRequestTimeout = + FileService(Ref ref, this._machineUUID, this._jRpcClient, this._dio) + : _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/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..32573e23c 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,9 +29,9 @@ 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())); + _dio = ref.watch(obicoApiClientProvider(uri.toString())); final Dio _dio; 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..ee691db43 100644 --- a/common/lib/service/payment_service.dart +++ b/common/lib/service/payment_service.dart @@ -7,11 +7,14 @@ 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'; +import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:stringr/stringr.dart'; @@ -23,54 +26,62 @@ 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(() => ) +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!'); + 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) { + if (kDebugMode) return true; return ref.watch(isSupporterAsyncProvider).valueOrNull == true; } @Riverpod(keepAlive: true) -FutureOr isSupporterAsync(IsSupporterAsyncRef ref) async { +FutureOr isSupporterAsync(Ref ref) async { + if (kDebugMode) 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 +91,7 @@ class PaymentService { final SettingService _settingService; - final PaymentServiceRef _ref; + final Ref _ref; Future initialize() async { if (kDebugMode) await Purchases.setLogLevel(LogLevel.info); @@ -123,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())); @@ -248,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'); + await Purchases.setAttributes({r'$deviceVersion': packageInfo.toString()}); + logger.i('Set device version to Purchases'); + }, + fireImmediately: true, + ); } } 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/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/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) { 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/common/pubspec.yaml b/common/pubspec.yaml index f5ccf7815..fea3d3936 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 - + collection: 1.19.0 # Required because dio_smart_retry is in conflict with the flutter sdk otherwise dependencies: flutter: @@ -18,28 +18,28 @@ 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 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 + purchases_flutter: ^8.2.2 #notification - awesome_notifications_core: ^0.9.3 - awesome_notifications: ^0.9.3 - awesome_notifications_fcm: ^0.9.3 - # live_activities: ^1.9.3 + awesome_notifications_core: ^0.10.0 + awesome_notifications: ^0.10.0 + awesome_notifications_fcm: ^0.10.0 + # live_activities: ^2.1.0 live_activities: git: url: https://github.com/Clon1998/flutter_live_activities @@ -54,16 +54,16 @@ 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 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.3.13 - custom_lint: ^0.6.4 - build_runner: ^2.4.9 + flutter_lints: ^5.0.0 + riverpod_lint: ^2.6.3 + custom_lint: ^0.7.0 + build_runner: ^2.4.13 freezed: ^2.5.2 - json_serializable: ^6.4.1 - riverpod_generator: ^2.4.3 - 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/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/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'); } 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/marshalling/printer/printer_builder_test.dart b/common/test/unit/marshalling/printer/printer_builder_test.dart index 78df35ec6..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,13 +189,14 @@ void main() { test('Update generic fan', () { final builder = PrinterBuilder.preview(); - builder.fans['hotend_fan'] = GenericFan(name: 'hotend_fan'); + final key = (ConfigFileObjectIdentifiers.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); }); }); } 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/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'); + }); + }); } diff --git a/docs/changelog.md b/docs/changelog.md index 51af2ff94..d441d1685 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,46 @@ # Mobileraker - Changelog +## [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 + 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) +- **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 + +- **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. + +- **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). +- **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 ### Enhancements 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) diff --git a/ios/Podfile b/ios/Podfile index 2c89436f2..3b67088d7 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' @@ -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 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", 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..1b55c42b8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -79,11 +79,13 @@ class MyApp extends ConsumerWidget { Locale('hu'), Locale('it'), Locale('nl'), + Locale('pl'), Locale('pt', 'BR'), Locale('ro'), Locale('ru'), Locale('tr'), Locale('uk'), + Locale('zh'), Locale('zh', 'CN'), Locale('zh', 'HK'), ], @@ -150,7 +152,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 +297,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 eb9bb44eb..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); @@ -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?), ); 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..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'; @@ -32,6 +34,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 +52,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), @@ -619,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, @@ -629,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}'); @@ -867,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'); } } 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/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(), 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..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'), ), @@ -353,7 +376,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/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/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/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) { 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', 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/components/filament_sensor_watcher.dart b/lib/ui/components/filament_sensor_watcher.dart index da6454bd9..7441abeb7 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,9 +34,9 @@ class FilamentSensorWatcher extends StatefulHookConsumerWidget { class _FilamentSensorWatcherState extends ConsumerState { DialogService get _dialogService => ref.read(dialogServiceProvider); - ProviderSubscription>>? _subscription; + ProviderSubscription>>? _subscription; - bool _enabled = true; + bool? _enabled; @override void initState() { @@ -46,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(); @@ -59,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); } @@ -70,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!; @@ -87,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, @@ -113,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 _triggered(_TriggeredRef ref, String machineUUID) { +Map<(ConfigFileObjectIdentifiers, String), bool> _triggered(Ref ref, String machineUUID) { ref.keepAlive(); return {}; } 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/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)); } 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/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), 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/fans_card.dart b/lib/ui/screens/dashboard/components/fans_card.dart index 6de10c74a..0e601bd10 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 }; @@ -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/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/dashboard/components/pins_card.dart b/lib/ui/screens/dashboard/components/pins_card.dart index fa75cc3f8..2b7ba9a9e 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'; @@ -363,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)); @@ -514,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) { @@ -543,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; @@ -600,7 +607,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); @@ -663,10 +670,10 @@ 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( + (ConfigFileObjectIdentifiers.led, 'preview led'): ConfigDumbLed( name: 'preview led', ), }, @@ -710,7 +717,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..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 @@ -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'; @@ -23,7 +22,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'; @@ -107,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), ); @@ -248,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( @@ -557,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) { @@ -590,9 +588,10 @@ class _Controller extends _$Controller { }); } - editTemperatureFan(TemperatureFan temperatureFan) { + void 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 @@ -614,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 { 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..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 @@ -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,59 @@ 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()) { + // 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); + })); + + 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 +313,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 +331,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, ); } 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/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/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..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'; @@ -35,6 +34,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'; @@ -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: [ @@ -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(), @@ -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(GcodeRef 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/files/details/video_player_page.dart b/lib/ui/screens/files/details/video_player_page.dart index fd5027142..dfdc517cb 100644 --- a/lib/ui/screens/files/details/video_player_page.dart +++ b/lib/ui/screens/files/details/video_player_page.dart @@ -32,7 +32,7 @@ class VideoPlayerPage extends ConsumerStatefulWidget { } class _VideoPlayerPageState extends ConsumerState { - late CachedVideoPlayerController videoPlayerController; + late CachedVideoPlayerPlusController videoPlayerController; late CustomVideoPlayerController _customVideoPlayerController; bool loading = true; double? fileDownloadProgress; @@ -48,7 +48,7 @@ class _VideoPlayerPageState extends ConsumerState { Map headers = dio.options.headers.cast(); - videoPlayerController = CachedVideoPlayerController.network(fileUri.toString(), httpHeaders: headers) + videoPlayerController = CachedVideoPlayerPlusController.networkUrl(fileUri, httpHeaders: headers) ..initialize() .then( (value) => setState(() { 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], }; diff --git a/lib/ui/screens/fullcam/full_cam_controller.dart b/lib/ui/screens/fullcam/full_cam_controller.dart index b29ef6c13..c4b5d5f08 100644 --- a/lib/ui/screens/fullcam/full_cam_controller.dart +++ b/lib/ui/screens/fullcam/full_cam_controller.dart @@ -7,17 +7,18 @@ 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]) +@Riverpod(dependencies: [initialCam]) class FullCamPageController extends _$FullCamPageController { @override WebcamInfo build() { 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), 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(); } } 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, ], ); } 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..3590e3e62 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; @@ -162,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/macro_group_list.dart b/lib/ui/screens/printers/edit/components/macro_group_list.dart index 92b9d5ad0..e4f3a4e60 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); } } @@ -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/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); } } diff --git a/lib/ui/screens/printers/edit/printers_edit_controller.dart b/lib/ui/screens/printers/edit/printers_edit_controller.dart index 283fc5df5..da08d5eb0 100644 --- a/lib/ui/screens/printers/edit/printers_edit_controller.dart +++ b/lib/ui/screens/printers/edit/printers_edit_controller.dart @@ -59,15 +59,22 @@ 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); } -@riverpod +@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, jrpcClientState]) +@Riverpod(dependencies: [currentlyEditing]) class WebcamListController extends _$WebcamListController { Machine get _machine => ref.read(currentlyEditingProvider); final List _camsToDelete = []; 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/setting/setting_page.dart b/lib/ui/screens/setting/setting_page.dart index 483397e12..c67c21f46 100644 --- a/lib/ui/screens/setting/setting_page.dart +++ b/lib/ui/screens/setting/setting_page.dart @@ -806,6 +806,15 @@ class _StateNotificationSettingField extends ConsumerWidget { children: PrintState.values.map((e) { var selected = value.contains(e); return FilterChip( + 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), 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/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/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/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; } 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(), diff --git a/pubspec.yaml b/pubspec.yaml index b44a5522d..8df135d13 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' @@ -23,13 +23,8 @@ 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 + collection: 1.19.0 # Required because dio_smart_retry is in conflict with the flutter sdk otherwise dependencies: flutter: @@ -44,19 +39,19 @@ 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 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 @@ -93,10 +88,10 @@ 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 - # live_activities: ^1.9.3 + awesome_notifications_core: ^0.10.0 + awesome_notifications: ^0.10.0 + awesome_notifications_fcm: ^0.10.0 + # live_activities: ^2.1.0 live_activities: # path: ../flutter_live_activities git: @@ -128,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 @@ -153,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: @@ -162,7 +157,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 @@ -171,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.4.3 + json_serializable: ^6.9.0 + riverpod_generator: ^2.6.3 # riverpod_lint makes it easier to work with Riverpod - riverpod_lint: ^2.3.13 + riverpod_lint: ^2.6.3 # 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 + flutter_launcher_icons: ^0.14.1 dart_code_metrics_presets: ^2.5.0