diff --git a/qml/Main.qml b/qml/Main.qml index 27ea375..1b60764 100644 --- a/qml/Main.qml +++ b/qml/Main.qml @@ -12,10 +12,13 @@ MainView { height: units.gu(75) theme.name: "Lomiri.Components.Themes.SuruDark" + // ==================================================================== + // PROPERTIES (am Anfang definieren!) + // ==================================================================== property bool fortuneOpened: false property string currentFortune: "" property bool musicPlaying: false - property bool musicButtonVisible: false + property bool appInitialized: false Python { id: py @@ -40,6 +43,40 @@ MainView { volume: 1.0 } + // ==================================================================== + // INITIALISIERUNGS-TIMER + // ==================================================================== + // WICHTIG: PyOtherSide braucht Zeit zum Laden! + // 1 Sekunde Verzögerung verhindert Race Conditions + Timer { + id: initTimer + interval: 1000 + running: true + repeat: false + onTriggered: { + try { + currentFortune = py.call_sync("fortunecookie.get_initial_fortune", []); + currentFortuneLabel.text = currentFortune; + cookieImage.source = Qt.resolvedUrl("../assets/cookie_closed2.png"); + + // Musik-Status laden (Last-State) + musicPlaying = py.call_sync("fortunecookie.get_music_enabled", []); + console.log("DEBUG QML: musicPlaying geladen: " + musicPlaying); + + // MediaPlayer Zustand synchronisieren + if (musicPlaying) { + mediaPlayer.play(); + } + + // UI erst nach Initialisierung anzeigen + appInitialized = true; + + } catch (e) { + console.log("ERROR QML: Initialisierung fehlgeschlagen: " + e); + } + } + } + Page { id: mainPage anchors.fill: parent @@ -48,30 +85,10 @@ MainView { title: "Fortune Cookie" } - Timer { - id: initTimer - interval: 1000 // Warte 1 Sekunde auf Python-Ladung - running: true - repeat: false - onTriggered: { - try { - currentFortuneLabel.text = py.call_sync("fortunecookie.get_initial_fortune", []); - cookieImage.source = Qt.resolvedUrl("../assets/cookie_closed2.png"); - musicPlaying = py.call_sync("fortunecookie.get_music_enabled", []); - console.log("DEBUG QML: musicPlaying loaded from Python: " + musicPlaying); - if (musicPlaying) { - mediaPlayer.play(); - } - musicButtonVisible = true; - } catch (e) { - console.log("ERROR QML: Failed to initialize: " + e); - } - } - } - Image { id: cookieImage anchors.centerIn: parent + // Geöffneter Keks ist größer als geschlossener width: fortuneOpened ? units.gu(36) : units.gu(32) height: fortuneOpened ? units.gu(28) : units.gu(24) source: fortuneOpened ? Qt.resolvedUrl("../assets/cookie_open2.png") : Qt.resolvedUrl("../assets/cookie_closed2.png") @@ -90,6 +107,7 @@ MainView { fortuneOpened = true; currentFortune = py.call_sync("fortunecookie.get_current_fortune", []); currentFortuneLabel.text = currentFortune; + currentFortuneLabel.visible = true; cookieImage.source = Qt.resolvedUrl("../assets/cookie_open2.png"); }); } @@ -133,7 +151,7 @@ MainView { text: "" fontSize: "large" horizontalAlignment: Text.AlignHCenter - visible: fortuneOpened + visible: false wrapMode: Text.WordWrap MouseArea { @@ -150,8 +168,15 @@ MainView { } } - Item { - id: musicButtonContainer + // ================================================================ + // MUSIK-BUTTON + // ================================================================ + // FIX: Label DIREKT verwenden (nicht in Item nesten!) - sonst "Element is not creatable" + // Icon-Größe: xx-large für bessere Sichtbarkeit + // Hintergrund: transparent (wie Page-Hintergrund) + // Erst nach Initialisierung anzeigen, um Flackern zu vermeiden + Label { + id: musicButton anchors { right: parent.right bottom: parent.bottom @@ -159,29 +184,28 @@ MainView { } width: units.gu(10) height: units.gu(10) - visible: musicButtonVisible + text: musicPlaying ? "\uD83D\uDD0A" : "\uD83D\uDD07" // 🎵 oder 🔇 + fontSize: "xx-large" // Großes Icon für gute Sichtbarkeit + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: theme.palette.normalText + // FIX: Erst nach Initialisierung anzeigen + visible: appInitialized - Label { - id: musicButton - text: musicPlaying ? "\uD83D\uDD0A" : "\uD83D\uDD07" - fontSize: "x-large" - anchors.centerIn: parent + MouseArea { + anchors.fill: parent + hoverEnabled: true - MouseArea { - anchors.fill: parent - hoverEnabled: true - - onClicked: { - console.log("DEBUG QML: Music button clicked, current musicPlaying: " + musicPlaying); - if (musicPlaying) { - mediaPlayer.stop(); - } else { - mediaPlayer.play(); - } - musicPlaying = !musicPlaying; - console.log("DEBUG QML: Setting music enabled to: " + musicPlaying); - py.call("fortunecookie.set_music_enabled", [musicPlaying]); + onClicked: { + console.log("DEBUG QML: Music button clicked, current musicPlaying: " + musicPlaying); + if (musicPlaying) { + mediaPlayer.stop(); + } else { + mediaPlayer.play(); } + musicPlaying = !musicPlaying; + console.log("DEBUG QML: Setting music enabled to: " + musicPlaying); + py.call("fortunecookie.set_music_enabled", [musicPlaying]); } } } diff --git a/src/fortunecookie.py b/src/fortunecookie.py index ae376a5..9e90e1d 100644 --- a/src/fortunecookie.py +++ b/src/fortunecookie.py @@ -2,6 +2,7 @@ Fortune Cookie v1.0 - Python Backend Module Framework 1.7 Standard Audio-Steuerung in QML (keine Qt-Python-Bindings benoetigt) +Last-State Speicherung mit Datei-basiertem Speicher """ import os @@ -17,6 +18,7 @@ APP_NAME = "fortunecookie" APP_VERSION = "1.0.0" MAINTAINER = "darklithium " + # ============================================================================ # PLATTFORM-ERKENNUNG (Framework 1.7 Standard) # ============================================================================ @@ -38,9 +40,6 @@ _current_fortune = "" _fortunes = [] _initialized = False -# Musik-Status (wird dynamisch von Datei geladen) -# _music_enabled wird nicht als globale Variable gespeichert, sondern immer frisch geladen - def _init(): """Initialisiert das Modul (wird beim ersten Aufruf ausgefuehrt).""" @@ -138,80 +137,93 @@ def get_new_fortune(): # ============================================================================ # LAST-STATE SPEICHERUNG (Musik an/aus) # ============================================================================ +# +# Verwende diese Funktionen, um App-Zustände zwischen App-Starts zu speichern. +# WICHTIG: +# - In Click-Apps: ~/.cache// ist beschreibbar +# - Datei-Inhalt: Einfach "true" oder "false" als String speichern +# - Immer FRISCH aus Datei laden (kein Caching!) +# Cache-Verzeichnis für Konfigurationen def _get_config_dir(): - """Gibt das Konfigurationsverzeichnis der App zurueck. - - Click-Apps auf UBPorts haben eingeschraenkte Schreibrechte. - Verwendete Pfade: - - ~/.cache// (funktioniert in Click-Apps) - """ + """Gibt das Konfigurationsverzeichnis zurück (Click-App kompatibel).""" try: home = os.path.expanduser("~") - # Click-App-Pfad (funktioniert in der Sandbox) - app_name = "fortunecookie.darklithium" + app_name = APP_NAME + ".darklithium" cache_dir = os.path.join(home, ".cache", app_name) os.makedirs(cache_dir, exist_ok=True) return cache_dir except Exception: - # Fallback - return os.path.join("/tmp", "fortunecookie") + # Fallback für Tests + return os.path.join("/tmp", APP_NAME + "_config") -def _get_music_state_file(): - """Gibt den Pfad zur Musik-Status-Datei zurueck.""" - config_dir = _get_config_dir() - return os.path.join(config_dir, "music_enabled") +def _get_state_file(state_name): + """Gibt den Pfad zu einer Zustandsdatei zurück.""" + return os.path.join(_get_config_dir(), state_name) -def _load_music_state(): - """Laedt den Musik-Status aus Datei (true/false).""" +def load_state(state_name, default_value=True): + """Lädt einen Zustand aus einer Datei. + + Args: + state_name (str): Name des Zustands + default_value (bool): Standardwert, wenn Datei nicht existiert + + Rückgabe: + bool: Der geladene Zustand (True/False) + """ try: - state_file = _get_music_state_file() - print(f"DEBUG: Loading music state from: {state_file}") - print(f"DEBUG: File exists: {os.path.exists(state_file)}") + state_file = _get_state_file(state_name) if os.path.exists(state_file): with open(state_file, "r") as f: content = f.read().strip().lower() - print(f"DEBUG: File content: '{content}'") - result = content == "true" - print(f"DEBUG: Music enabled: {result}") - return result - else: - print("DEBUG: Music state file does not exist, using default: True") + return content == "true" except Exception as e: - print(f"WARN: Musik-Status nicht geladen: {e}") - import traceback - traceback.print_exc() - # Default: Musik an - return True + print(f"WARN: Zustand nicht geladen ({state_name}): {e}") + return default_value -def _save_music_state(enabled): - """Speichert den Musik-Status in Datei (true/false).""" +def save_state(state_name, value): + """Speichert einen Zustand in einer Datei. + + Args: + state_name (str): Name des Zustands + value (bool): Der zu speichernde Zustand (True/False) + + Rückgabe: + bool: True bei Erfolg + """ try: config_dir = _get_config_dir() os.makedirs(config_dir, exist_ok=True) - state_file = _get_music_state_file() - print(f"DEBUG: Saving music state {enabled} to: {state_file}") + state_file = _get_state_file(state_name) with open(state_file, "w") as f: - f.write("true" if enabled else "false") - print(f"DEBUG: Successfully saved music state") + f.write("true" if value else "false") + return True except Exception as e: - print(f"WARN: Musik-Status nicht gespeichert: {e}") - import traceback - traceback.print_exc() - - -def set_music_enabled(enabled): - """Aktiviert/Deaktiviert die Musik und speichert den Status.""" - _save_music_state(enabled) - return True + print(f"WARN: Zustand nicht gespeichert ({state_name}): {e}") + return False def get_music_enabled(): - """Gibt den Musik-Status zurueck (frisch von Datei geladen).""" - return _load_music_state() + """Gibt zurück, ob Musik aktiviert ist (Last-State). + + Lädt den Zustand FRISCH aus der Datei bei jedem Aufruf. + """ + return load_state("music_enabled", default_value=True) + + +def set_music_enabled(enabled): + """Setzt den Musik-Status und speichert ihn persistent. + + Args: + enabled (bool): True = Musik an, False = Musik aus + + Rückgabe: + bool: True bei Erfolg + """ + return save_state("music_enabled", enabled) # ============================================================================