diff --git a/assets/cookie_closed.png b/assets/cookie_closed.png deleted file mode 100644 index 605038c..0000000 Binary files a/assets/cookie_closed.png and /dev/null differ diff --git a/assets/cookie_closed2.png b/assets/cookie_closed2.png new file mode 100644 index 0000000..957c484 Binary files /dev/null and b/assets/cookie_closed2.png differ diff --git a/assets/cookie_crack.mp3 b/assets/cookie_crack.mp3 new file mode 100644 index 0000000..aa48bcb Binary files /dev/null and b/assets/cookie_crack.mp3 differ diff --git a/assets/cookie_open.png b/assets/cookie_open.png deleted file mode 100644 index 3f8a42c..0000000 Binary files a/assets/cookie_open.png and /dev/null differ diff --git a/assets/cookie_open2.png b/assets/cookie_open2.png new file mode 100644 index 0000000..25c1b6c Binary files /dev/null and b/assets/cookie_open2.png differ diff --git a/fortunecookie.desktop.in b/fortunecookie.desktop.in index 596a9fe..ebfedfb 100644 --- a/fortunecookie.desktop.in +++ b/fortunecookie.desktop.in @@ -4,7 +4,7 @@ Type=Application Name=Fortune Cookie Comment=Glückskeks App mit Sprüchen, Musik und einfacher Verwaltung Exec=qmlscene %U qml/Main.qml -Icon=assets/cookie_closed.png +Icon=assets/cookie_closed2.png Terminal=false Categories=Utility; X-Lomiri-Touch=true diff --git a/qml/Main.qml b/qml/Main.qml index 0418047..27ea375 100644 --- a/qml/Main.qml +++ b/qml/Main.qml @@ -1,5 +1,6 @@ import QtQuick 2.7 import QtQuick.Layouts 1.3 +import QtMultimedia 5.0 import Lomiri.Components 1.3 import Lomiri.Components.Popups 1.3 import io.thp.pyotherside 1.4 @@ -11,97 +12,114 @@ MainView { height: units.gu(75) theme.name: "Lomiri.Components.Themes.SuruDark" - // ==================================================================== - // 1. PYTHON-MODUL (STANDARD 1.7) - // ==================================================================== + property bool fortuneOpened: false + property string currentFortune: "" + property bool musicPlaying: false + property bool musicButtonVisible: false + Python { id: py Component.onCompleted: { addImportPath(Qt.resolvedUrl("../src")); importModule("fortunecookie", function() { console.log("Python-Modul fortunecookie geladen"); - // Initialisierung - currentFortuneLabel.text = py.call_sync("fortunecookie.get_initial_fortune", []); - cookieImage.source = "assets/cookie_closed.png"; - fortuneOpened = false; }); } } - // ==================================================================== - // 2. APP-ZUSTAND (STANDARD 1.7) - // ==================================================================== - property bool fortuneOpened: false - property string currentFortune: "" - property bool musicPlaying: false + MediaPlayer { + id: mediaPlayer + source: Qt.resolvedUrl("../assets/chinese_music.mp3") + loops: MediaPlayer.Infinite + volume: 0.5 + } + + MediaPlayer { + id: crackMediaPlayer + source: Qt.resolvedUrl("../assets/cookie_crack.mp3") + volume: 1.0 + } - // ==================================================================== - // 3. HAUPTSEITE - // ==================================================================== Page { id: mainPage anchors.fill: parent - // Header header: PageHeader { title: "Fortune Cookie" } - // ================================================================ - // COOKIE & SPRUCH - // ================================================================ + 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); + } + } + } - // Cookie-Image (zentral) Image { id: cookieImage anchors.centerIn: parent - width: units.gu(30) - height: units.gu(30) - source: fortuneOpened ? "assets/cookie_open.png" : "assets/cookie_closed.png" + 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") + fillMode: Image.PreserveAspectFit - // Wisch-Geste nach oben MouseArea { anchors.fill: parent property real startY: 0 - onPressed: { - startY = mouseY - } + onPressed: startY = mouseY onReleased: { - // Ende der Geste - pruufen ob nach oben gewischt if (mouseY < startY - units.gu(2)) { - // Wisch nach oben -> Cookie oeffnen py.call("fortunecookie.open_fortune", [], function() { + crackMediaPlayer.play(); fortuneOpened = true; currentFortune = py.call_sync("fortunecookie.get_current_fortune", []); currentFortuneLabel.text = currentFortune; - cookieImage.source = "assets/cookie_open.png"; + cookieImage.source = Qt.resolvedUrl("../assets/cookie_open2.png"); }); } } } - // Tap auf Cookie (wenn geschlossen) MouseArea { anchors.fill: parent hoverEnabled: true onClicked: { if (!fortuneOpened) { - // Cookie oeffnen (gleiche Funktion wie Wisch nach oben) py.call("fortunecookie.open_fortune", [], function() { + crackMediaPlayer.play(); fortuneOpened = true; currentFortune = py.call_sync("fortunecookie.get_current_fortune", []); currentFortuneLabel.text = currentFortune; - cookieImage.source = "assets/cookie_open.png"; + currentFortuneLabel.visible = true; + cookieImage.source = Qt.resolvedUrl("../assets/cookie_open2.png"); }); + } else { + fortuneOpened = false; + currentFortuneLabel.text = ""; + currentFortuneLabel.visible = false; + cookieImage.source = Qt.resolvedUrl("../assets/cookie_closed2.png"); } } } } - // Fortune-Text (erscheint nach dem Oeffnen) Label { id: currentFortuneLabel anchors { @@ -118,7 +136,6 @@ MainView { visible: fortuneOpened wrapMode: Text.WordWrap - // Tap auf Spruch -> neuer Cookie MouseArea { anchors.fill: parent @@ -127,34 +144,45 @@ MainView { fortuneOpened = false; currentFortune = py.call_sync("fortunecookie.get_current_fortune", []); currentFortuneLabel.text = currentFortune; - cookieImage.source = "assets/cookie_closed.png"; + cookieImage.source = Qt.resolvedUrl("../assets/cookie_closed2.png"); }); } } } - // ================================================================ - // MUSIK BUTTON (rechts unten) - // ================================================================ - Button { - id: musicButton + Item { + id: musicButtonContainer anchors { right: parent.right bottom: parent.bottom margins: units.gu(2) } - width: units.gu(8) - height: units.gu(8) - text: musicPlaying ? "\uD83D\uDD07" : "\uD83D\uDD0A" - fontSize: "x-large" + width: units.gu(10) + height: units.gu(10) + visible: musicButtonVisible - onClicked: { - if (musicPlaying) { - py.call("fortunecookie.stop_music", []); - } else { - py.call("fortunecookie.start_music", []); + Label { + id: musicButton + text: musicPlaying ? "\uD83D\uDD0A" : "\uD83D\uDD07" + fontSize: "x-large" + anchors.centerIn: parent + + 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]); + } } - musicPlaying = !musicPlaying; } } } diff --git a/src/fortunecookie.py b/src/fortunecookie.py index 3444c30..ae376a5 100644 --- a/src/fortunecookie.py +++ b/src/fortunecookie.py @@ -1,6 +1,7 @@ """ Fortune Cookie v1.0 - Python Backend Module Framework 1.7 Standard +Audio-Steuerung in QML (keine Qt-Python-Bindings benoetigt) """ import os @@ -37,18 +38,13 @@ _current_fortune = "" _fortunes = [] _initialized = False -# Musik-Status -_music_enabled = True -_music_playing = False - -# Medien-Player (wird lazy initialisiert) -_media_player = None -_cookie_crack_sound = None +# 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).""" - global _fortunes, _initialized, _music_enabled + global _fortunes, _initialized if _initialized: return True @@ -56,9 +52,6 @@ def _init(): # Lade Fortunes _load_fortunes() - # Lade Last-State - _music_enabled = _load_music_state() - _initialized = True return True @@ -84,6 +77,9 @@ def _load_fortunes(): if isinstance(data, list): _fortunes = data elif isinstance(data, dict): + # Lade deutsche Sprüche, falls vorhanden, sonst englische + _fortunes = data.get("de", data.get("en", [])) + elif isinstance(data, dict) and "fortunes" in data: _fortunes = data.get("fortunes", []) return @@ -95,12 +91,14 @@ def _load_fortunes(): ] except Exception: - # Fallback: Einige Standard-Sprueche - _fortunes = [ - "Ein guter Tag beginnt mit einem Laecheln.", - "Das Glueck liegt in den kleinen Dingen.", - "Geduld ist eine Tugend.", - ] + _fortunes = [] + + +def _get_random_fortune(): + """Gibt einen zufaelligen Spruch zurueck.""" + if not _fortunes: + _load_fortunes() + return random.choice(_fortunes) if _fortunes else "Keine Sprueche verfguebar." def get_initial_fortune(): @@ -112,16 +110,13 @@ def get_initial_fortune(): def open_fortune(): - """Oeffnet den Fortune Cookie (neuer Spruch + Knack-Geraeusch).""" + """Oeffnet den Fortune Cookie (neuer Spruch).""" _init() global _current_fortune # Neuer Spruch _current_fortune = _get_random_fortune() - # Knack-Geraeusch abspielen (wenn verfguebar) - _play_crack_sound() - return True @@ -140,164 +135,83 @@ def get_new_fortune(): return _current_fortune -def _get_random_fortune(): - """Gibt einen zufaelligen Spruch zurueck.""" - if not _fortunes: - _load_fortunes() - return random.choice(_fortunes) if _fortunes else "Keine Sprueche verfguebar." - - # ============================================================================ -# MUSIK-LOGIK +# LAST-STATE SPEICHERUNG (Musik an/aus) # ============================================================================ -def start_music(): - """Startet die Hintergrundmusik.""" - _init() - global _music_playing, _media_player, _music_enabled - - if not _music_enabled: - return False - +def _get_config_dir(): + """Gibt das Konfigurationsverzeichnis der App zurueck. + + Click-Apps auf UBPorts haben eingeschraenkte Schreibrechte. + Verwendete Pfade: + - ~/.cache// (funktioniert in Click-Apps) + """ try: - # Medien-Player initialisieren (wenn nicht vorhanden) - if _media_player is None: - from PySide2 import QtMultimedia, QtCore - - _media_player = QtMultimedia.QMediaPlayer() - audio_output = QtMultimedia.QAudioOutput() - _media_player.setAudioOutput(audio_output) - - # Musik-Datei laden - music_path = get_asset_path("chinese_music.mp3") - _media_player.setSource(QtCore.QUrl.fromLocalFile(music_path)) - _media_player.setLoops(QtMultimedia.QMediaPlayer.Infinite) - _media_player.setVolume(50) - - _media_player.play() - _music_playing = True - return True - + home = os.path.expanduser("~") + # Click-App-Pfad (funktioniert in der Sandbox) + app_name = "fortunecookie.darklithium" + cache_dir = os.path.join(home, ".cache", app_name) + os.makedirs(cache_dir, exist_ok=True) + return cache_dir except Exception: - return False + # Fallback + return os.path.join("/tmp", "fortunecookie") -def stop_music(): - """Stoppt die Hintergrundmusik.""" - global _music_playing, _media_player - - if _media_player is not None: - try: - _media_player.stop() - except Exception: - pass - _music_playing = False - - return True - - -def toggle_music(): - """Wechselt Musik-Status (an/aus).""" - if _music_playing: - return stop_music() - else: - return start_music() - - -def set_music_enabled(enabled): - """Aktiviert/Deaktiviert Musik generell.""" - global _music_enabled - _music_enabled = enabled - _save_music_state(enabled) - return enabled - - -def get_music_enabled(): - """Gibt zurueck, ob Musik aktiviert ist.""" - global _music_enabled - return _music_enabled - - -def get_music_playing(): - """Gibt zurueck, ob Musik gerade spielt.""" - global _music_playing - return _music_playing - - -# ============================================================================ -# LAST-STATE SPEICHERUNG -# ============================================================================ - -def _get_data_dir(): - """Gibt das Datenverzeichnis zurueck.""" - if "CLICK" in os.environ: - return os.path.join( - os.path.expanduser("~"), ".local", "share", f"{APP_NAME}.darklithium" - ) - else: - return os.path.join( - os.path.expanduser("~"), ".local", "share", f"{APP_NAME}" - ) +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 _load_music_state(): - """Laedt den Musik-Status aus Datei.""" + """Laedt den Musik-Status aus Datei (true/false).""" try: - state_file = os.path.join(_get_data_dir(), "music_state.json") + 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)}") if os.path.exists(state_file): with open(state_file, "r") as f: - data = json.load(f) - return data.get("enabled", True) - except Exception: - pass + 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") + except Exception as e: + print(f"WARN: Musik-Status nicht geladen: {e}") + import traceback + traceback.print_exc() + # Default: Musik an return True def _save_music_state(enabled): - """Speichert den Musik-Status in Datei.""" + """Speichert den Musik-Status in Datei (true/false).""" try: - data_dir = _get_data_dir() - os.makedirs(data_dir, exist_ok=True) - state_file = os.path.join(data_dir, "music_state.json") + 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}") with open(state_file, "w") as f: - json.dump({"enabled": enabled}, f) - except Exception: - pass + f.write("true" if enabled else "false") + print(f"DEBUG: Successfully saved music state") + except Exception as e: + print(f"WARN: Musik-Status nicht gespeichert: {e}") + import traceback + traceback.print_exc() -def _play_crack_sound(): - """Spielt das Knack-Geraeusch ab.""" - try: - global _cookie_crack_sound - - if _cookie_crack_sound is None: - from PySide2 import QtMultimedia, QtCore - - _cookie_crack_sound = QtMultimedia.QMediaPlayer() - audio_output = QtMultimedia.QAudioOutput() - _cookie_crack_sound.setAudioOutput(audio_output) - - crack_path = get_asset_path("cookie_crack.mp3") - if os.path.exists(crack_path): - _cookie_crack_sound.setSource(QtCore.QUrl.fromLocalFile(crack_path)) - _cookie_crack_sound.setVolume(100) - _cookie_crack_sound.play() - except Exception: - pass +def set_music_enabled(enabled): + """Aktiviert/Deaktiviert die Musik und speichert den Status.""" + _save_music_state(enabled) + return True -# ============================================================================ -# DATENVERZEICHNIS (fuer zukuenftige Erweiterungen) -# ============================================================================ - -def get_data_dir(): - """Gibt das Datenverzeichnis der App zurueck.""" - return _get_data_dir() - - -def get_fortunes_file_path(): - """Gibt den Pfad zur Fortunes-Datei zurueck.""" - return os.path.join(_get_data_dir(), "fortunes.json") +def get_music_enabled(): + """Gibt den Musik-Status zurueck (frisch von Datei geladen).""" + return _load_music_state() # ============================================================================