diff --git a/CMakeLists.txt b/CMakeLists.txt index c48cb89..6988090 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,11 +37,14 @@ configure_file(manifest.json.in ${CMAKE_CURRENT_BINARY_DIR}/manifest.json) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifest.json DESTINATION ${CMAKE_INSTALL_PREFIX}) install(FILES ${PROJECT_NAME}.apparmor DESTINATION ${DATA_DIR}) +# Desktop-Datei konfigurieren (ersetzt Variablen und entfernt Kommentare) +configure_file(${PROJECT_NAME}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.desktop) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.desktop DESTINATION ${DATA_DIR}) + # Dateien installieren install(DIRECTORY assets DESTINATION ${DATA_DIR}) install(DIRECTORY src DESTINATION ${DATA_DIR}) install(DIRECTORY qml DESTINATION ${DATA_DIR}) -install(FILES ${PROJECT_NAME}.desktop.in DESTINATION ${DATA_DIR}) # ============================================================================ # OPTIONAL: Für Apps mit Übersetzungen (po/) diff --git a/assets/cookie_crack_placeholder.txt b/assets/cookie_crack_placeholder.txt deleted file mode 100644 index 5d853f1..0000000 --- a/assets/cookie_crack_placeholder.txt +++ /dev/null @@ -1 +0,0 @@ -# Platzhalter: Hier soll cookie_crack.mp3 oder cookie_crack.wav hingelegt werden diff --git a/fortunecookie.desktop.in b/fortunecookie.desktop.in index 43d2ed2..596a9fe 100644 --- a/fortunecookie.desktop.in +++ b/fortunecookie.desktop.in @@ -9,35 +9,3 @@ Terminal=false Categories=Utility; X-Lomiri-Touch=true X-Ubuntu-Applications=fortunecookie - -/* - * DESKTOP.FILE TEMPLATE für Ubuntu Touch Apps - * Basierend auf metime und Referenz-App (Version 1.7) - * - * VERWENDUNG: - * 1. Kopiere diese Datei nach fortunecookie.desktop.in in deinem Projekt - * 2. Ersetze alle <...> Platzhalter mit deinen Werten - * 3. Benenne die Datei in fortunecookie.desktop.in um - * - * WICHTIG (1.7): - * ✅ Exec=qmlscene %U qml/Main.qml (für pure-QML-Apps mit pure-qml-cmake) - * ✅ X-Lomiri-Touch=true (für Lomiri/Ubuntu Touch) - * ✅ X-Ubuntu-Applications=fortunecookie (App-Name ohne .desktop Endung) - * - * ❌ VERMEIDEN: - * - Exec=fortunecookie (funktioniert nicht mit pure-qml-cmake!) - * - Terminal=true (UI-Apps brauchen kein Terminal) - * - * Beispiel für eine fertige Datei (metime.desktop.in): - * [Desktop Entry] - * Version=1.0 - * Type=Application - * Name=Metime - * Comment=Mein Metime Manager - * Exec=qmlscene %U qml/Main.qml - * Icon=assets/cookie_closed.png - * Terminal=false - * Categories=Utility; - * X-Lomiri-Touch=true - * X-Ubuntu-Applications=metime - */ diff --git a/manifest.json.in b/manifest.json.in index 6457144..6f7a411 100644 --- a/manifest.json.in +++ b/manifest.json.in @@ -1,9 +1,9 @@ { "name": "fortunecookie.darklithium", "title": "Fortune Cookie", - "version": "0.1.0", + "version": "1.0.0", "description": "Glückskeks App mit Sprüchen, Musik und einfacher Listenverwaltung", - "maintainer": "Christian Franz ", + "maintainer": "darklithium ", "architecture": "all", "framework": "ubuntu-sdk-20.04", "hooks": { @@ -13,41 +13,3 @@ } } } - -/* - * MANIFEST.JSON.IN TEMPLATE für Ubuntu Touch Apps - * Basierend auf metime und Referenz-App (Version 1.7) - * - * VERWENDUNG: - * 1. Kopiere diese Datei nach manifest.json.in in deinem Projekt - * 2. Ersetze alle <...> Platzhalter mit deinen Werten - * - * WICHTIG (1.7): - * ✅ Nur Standardfelder verwenden! - * ✅ architecture: "all" (pure-qml-cmake erzwingt das) - * ✅ framework: "ubuntu-sdk-20.04" - * ✅ maintainer: Format "Vorname Nachname " (keine spitzen Klammern um den Namen!) - * - * ❌ VERMEIDEN (schlägt Validierung fehl): - * - "permissions": [] - * - "policy_groups": [] - * - "read_path": [] - * - "write_path": [] - * - * Beispiel für eine fertige Datei: - * { - * "name": "meine-app.darklithium", - * "title": "Meine App", - * "version": "0.1.0", - * "description": "Beschreibung", - * "maintainer": "Christian Franz ", - * "architecture": "all", - * "framework": "ubuntu-sdk-20.04", - * "hooks": { - * "meine-app": { - * "apparmor": "meine-app.apparmor", - * "desktop": "meine-app.desktop" - * } - * } - * } - */ diff --git a/qml/Main.qml b/qml/Main.qml index 349b595..0418047 100644 --- a/qml/Main.qml +++ b/qml/Main.qml @@ -1,127 +1,161 @@ -/* - * UNIVERSELLER QML Haupt-Template für Ubuntu Touch Apps - * Basierend auf metime und Referenz-App (Version 1.7) - * - * VERWENDUNG: - * 1. Kopiere diese Datei nach qml/Main.qml - * 2. Ersetze '' mit deinem App-Namen - * 3. Passe Titel und UI-Elemente an - * 4. Füge deine Python-Modul-Imports hinzu - * - * WICHTIG (1.7): - * - Immer QtQuick 2.7 und Lomiri.Components 1.3 verwenden - * - QtQuick.Layouts 1.3 für Layouts importieren - * - fontSize als STRING verwenden ("large", "x-large", "medium") - * - PageFooter existiert NICHT → als Label implementieren - * - PyOtherSide mit Qt.resolvedUrl("../src") einbinden - */ - import QtQuick 2.7 -import QtQuick.Layouts 1.3 // ✅ NEU 1.7: Notwendig für ColumnLayout! +import QtQuick.Layouts 1.3 import Lomiri.Components 1.3 import Lomiri.Components.Popups 1.3 import io.thp.pyotherside 1.4 MainView { id: root - objectName: 'mainView' - - // APP META-DATEN (Anpassen!) - applicationName: '.darklithium' + applicationName: "fortunecookie.darklithium" width: units.gu(45) height: units.gu(75) theme.name: "Lomiri.Components.Themes.SuruDark" // ==================================================================== - // PYTHON-MODUL (STANDARD 1.7) + // 1. PYTHON-MODUL (STANDARD 1.7) // ==================================================================== - Python { id: py Component.onCompleted: { - // ✅ STANDARD 1.7: Qt.resolvedUrl funktioniert! addImportPath(Qt.resolvedUrl("../src")); - - importModule('', function() { - console.log("Python-Modul geladen"); - // Initialisierung nach erfolgreicher Modul-Ladung - statusLabel.text = py.call_sync(".get_status_text", []); - footerLabel.text = py.call_sync(".get_platform", []); + 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; }); } - onError: { - console.log('Python Fehler: ' + traceback); - } } // ==================================================================== - // HAUPTSSeITE (Anpassen nach Bedarf) + // 2. APP-ZUSTAND (STANDARD 1.7) // ==================================================================== + property bool fortuneOpened: false + property string currentFortune: "" + property bool musicPlaying: false + // ==================================================================== + // 3. HAUPTSEITE + // ==================================================================== Page { id: mainPage anchors.fill: parent // Header header: PageHeader { - title: "" // App-Name anpassen + title: "Fortune Cookie" } - // Hauptinhalt (ColumnLayout für vertikale Anordnung) - ColumnLayout { - anchors.fill: parent - spacing: units.gu(2) // 2 GU Abstand zwischen Elementen + // ================================================================ + // COOKIE & SPRUCH + // ================================================================ - // Status-Label (optional) - Label { - id: statusLabel - text: "Lade..." - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - fontSize: "large" // ✅ NEU 1.7: String-Wert! + // 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" + + // Wisch-Geste nach oben + MouseArea { + anchors.fill: parent + property real startY: 0 + + 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() { + fortuneOpened = true; + currentFortune = py.call_sync("fortunecookie.get_current_fortune", []); + currentFortuneLabel.text = currentFortune; + cookieImage.source = "assets/cookie_open.png"; + }); + } + } } - // Hauptinhalt hier einfügen - // Beispiel: Textfeld - Label { - id: contentLabel - text: "Hier steht Beispieltext" - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - fontSize: "x-large" // ✅ NEU 1.7: String-Wert! - } - - // Beispiel: Button - Button { - id: testButton - text: "Test Button" - Layout.fillWidth: false - Layout.preferredWidth: units.gu(20) // 200 DP (Touch-optimiert!) - Layout.preferredHeight: units.gu(8) // 80 DP + // Tap auf Cookie (wenn geschlossen) + MouseArea { + anchors.fill: parent + hoverEnabled: true onClicked: { - if (py) { - py.call(".on_button_click", [], function() { - contentLabel.text = py.call_sync(".get_content_text", []); + if (!fortuneOpened) { + // Cookie oeffnen (gleiche Funktion wie Wisch nach oben) + py.call("fortunecookie.open_fortune", [], function() { + fortuneOpened = true; + currentFortune = py.call_sync("fortunecookie.get_current_fortune", []); + currentFortuneLabel.text = currentFortune; + cookieImage.source = "assets/cookie_open.png"; }); } } } } - // ================================================================ - // FOOTER (Workaround für Lomiri.Components 1.3) - // ================================================================ - // ⚠️ WICHTIG: PageFooter existiert NICHT in Lomiri.Components 1.3! + // Fortune-Text (erscheint nach dem Oeffnen) Label { - id: footerLabel - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.bottomMargin: units.gu(2) - text: "Plattform: ?" - fontSize: "medium" // ✅ NEU 1.7: String-Wert! + id: currentFortuneLabel + anchors { + top: cookieImage.bottom + topMargin: units.gu(2) + left: parent.left + right: parent.right + leftMargin: units.gu(2) + rightMargin: units.gu(2) + } + text: "" + fontSize: "large" horizontalAlignment: Text.AlignHCenter + visible: fortuneOpened + wrapMode: Text.WordWrap + + // Tap auf Spruch -> neuer Cookie + MouseArea { + anchors.fill: parent + + onClicked: { + py.call("fortunecookie.get_new_fortune", [], function() { + fortuneOpened = false; + currentFortune = py.call_sync("fortunecookie.get_current_fortune", []); + currentFortuneLabel.text = currentFortune; + cookieImage.source = "assets/cookie_closed.png"; + }); + } + } + } + + // ================================================================ + // MUSIK BUTTON (rechts unten) + // ================================================================ + Button { + id: musicButton + 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" + + onClicked: { + if (musicPlaying) { + py.call("fortunecookie.stop_music", []); + } else { + py.call("fortunecookie.start_music", []); + } + musicPlaying = !musicPlaying; + } } } } diff --git a/src/fortunecookie.py b/src/fortunecookie.py index 8c38fe1..3444c30 100644 --- a/src/fortunecookie.py +++ b/src/fortunecookie.py @@ -1,194 +1,318 @@ """ -UNIVERSELLES PYTHON-MODUL TEMPLATE für Ubuntu Touch Apps -Basierend auf metime und Referenz-App (Version 1.7) - -VERWENDUNG: -1. Kopiere diese Datei nach src/.py -2. Ersetze '' mit deinem App-Namen -3. Füge deine Funktionen hinzu - -WICHTIG (1.7): -- KEINE dbus-Importe in diesem Modul! (PyOtherSide-Kompatibilität) -- Keine Top-Level print() Statements -- Keine Top-Level Code-Ausführung -- Ein Modul reicht für 90% der Apps (modulare Trennung ist OPTIONAL) +Fortune Cookie v1.0 - Python Backend Module +Framework 1.7 Standard """ import os +import random +import json import platform +from pathlib import Path # ============================================================================ -# APP-METADATEN (Anpassen!) +# APP-METADATEN # ============================================================================ - -APP_NAME = "" # App-Name (z. B. "meine-app") -APP_VERSION = "0.1.0" # Version (Semantic Versioning) -MAINTAINER = "Christian Franz " # Maintainer +APP_NAME = "fortunecookie" +APP_VERSION = "1.0.0" +MAINTAINER = "darklithium " # ============================================================================ -# PLATTFORM-ERKENNUNG (1.7 Standard) +# PLATTFORM-ERKENNUNG (Framework 1.7 Standard) # ============================================================================ def get_platform(): - """ - Gibt die aktuelle Plattform zurück (arm64/amd64). - - Rückgabe: - str: "arm64" oder "amd64" - """ + """Gibt die aktuelle Plattform zurueck (arm64/amd64).""" machine = platform.machine().lower() if "arm" in machine or "aarch" in machine: return "arm64" return "amd64" -# ============================================================================ -# STATUS-FUNKTIONEN -# ============================================================================ - -def get_status_text(): - """ - Gibt einen Status-Text für die UI zurück. - - Rückgabe: - str: Status-Text mit App-Name, Version und Plattform - """ - plat = get_platform() - return f"{APP_NAME} v{APP_VERSION} | Plattform: {plat}" - -def get_platform(): - """Alias für get_platform (für QML-Aufrufe).""" - return get_platform() # ============================================================================ -# UI-FUNKTIONEN (Beispiele - anpassen!) +# FORTUNE-LOGIK # ============================================================================ -def get_content_text(): - """ - Gibt den aktuellen Inhaltstext zurück. +# Globale Variablen +_current_fortune = "" +_fortunes = [] +_initialized = False - Rückgabe: - str: Text für die UI - """ - return "Button wurde geklickt! (Python → QML)" +# Musik-Status +_music_enabled = True +_music_playing = False -def on_button_click(): - """ - Wird aufgerufen, wenn der Button in QML geklickt wird. +# Medien-Player (wird lazy initialisiert) +_media_player = None +_cookie_crack_sound = None - Rückgabe: - bool: True bei Erfolg - """ - print("Button clicked") # Log für Debugging + +def _init(): + """Initialisiert das Modul (wird beim ersten Aufruf ausgefuehrt).""" + global _fortunes, _initialized, _music_enabled + + if _initialized: + return True + + # Lade Fortunes + _load_fortunes() + + # Lade Last-State + _music_enabled = _load_music_state() + + _initialized = True return True + +def _load_fortunes(): + """Laedt alle Sprueche aus assets/fortunes.json.""" + global _fortunes + + if _fortunes: + return + + try: + # Versuche verschiedene Pfade + possible_paths = [ + os.path.join(os.path.dirname(__file__), "..", "assets", "fortunes.json"), + os.path.join("assets", "fortunes.json"), + ] + + for fortunes_path in possible_paths: + if os.path.exists(fortunes_path): + with open(fortunes_path, "r", encoding="utf-8") as f: + data = json.load(f) + if isinstance(data, list): + _fortunes = data + elif isinstance(data, dict): + _fortunes = data.get("fortunes", []) + return + + # Fallback: Standard-Sprueche + _fortunes = [ + "Ein guter Tag beginnt mit einem Laecheln.", + "Das Glueck liegt in den kleinen Dingen.", + "Geduld ist eine Tugend.", + ] + + 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.", + ] + + +def get_initial_fortune(): + """Gibt einen zufaelligen Spruch fuer den Start zurueck.""" + _init() + global _current_fortune + _current_fortune = _get_random_fortune() + return _current_fortune + + +def open_fortune(): + """Oeffnet den Fortune Cookie (neuer Spruch + Knack-Geraeusch).""" + _init() + global _current_fortune + + # Neuer Spruch + _current_fortune = _get_random_fortune() + + # Knack-Geraeusch abspielen (wenn verfguebar) + _play_crack_sound() + + return True + + +def get_current_fortune(): + """Gibt den aktuellen Spruch zurueck.""" + _init() + global _current_fortune + return _current_fortune + + +def get_new_fortune(): + """Gibt einen neuen Spruch zurueck (Cookie schliesst sich).""" + _init() + global _current_fortune + _current_fortune = _get_random_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." + + # ============================================================================ -# DATENVERZEICHNIS (Optional - für persistente Daten) +# MUSIK-LOGIK +# ============================================================================ + +def start_music(): + """Startet die Hintergrundmusik.""" + _init() + global _music_playing, _media_player, _music_enabled + + if not _music_enabled: + return False + + 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 + + except Exception: + return False + + +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 _load_music_state(): + """Laedt den Musik-Status aus Datei.""" + try: + state_file = os.path.join(_get_data_dir(), "music_state.json") + 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 + return True + + +def _save_music_state(enabled): + """Speichert den Musik-Status in Datei.""" + try: + data_dir = _get_data_dir() + os.makedirs(data_dir, exist_ok=True) + state_file = os.path.join(data_dir, "music_state.json") + with open(state_file, "w") as f: + json.dump({"enabled": enabled}, f) + except Exception: + pass + + +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 + + +# ============================================================================ +# DATENVERZEICHNIS (fuer zukuenftige Erweiterungen) # ============================================================================ def get_data_dir(): - """ - Gibt das Datenverzeichnis der App zurück. + """Gibt das Datenverzeichnis der App zurueck.""" + return _get_data_dir() - Rückgabe: - str: Pfad zum Datenverzeichnis - """ - app_dir = os.path.join( - os.path.expanduser("~"), - ".local", - "share", - f"{APP_NAME}.darklithium" - ) - os.makedirs(app_dir, exist_ok=True) - return app_dir -def get_data_file_path(filename): - """ - Gibt den Pfad zu einer Daten-Datei zurück. +def get_fortunes_file_path(): + """Gibt den Pfad zur Fortunes-Datei zurueck.""" + return os.path.join(_get_data_dir(), "fortunes.json") - Args: - filename (str): Dateiname - - Rückgabe: - str: Vollständiger Pfad zur Datei - """ - return os.path.join(get_data_dir(), filename) # ============================================================================ -# BEISPIEL: DATEN LADEN/SPEICHERN +# PLATTFORM-SPEZIFISCHE PFADERMITTLUNG # ============================================================================ -def load_data(filename="data.json"): - """ - Lädt JSON-Daten aus einer Datei. +def get_asset_path(filename): + """Gibt den Pfad zu einer Asset-Datei zurueck (funktioniert in Click & Desktop).""" + possible_paths = [ + os.path.join(os.path.dirname(__file__), "..", "assets", filename), + os.path.join("assets", filename), + ] - Args: - filename (str): Dateiname + for path in possible_paths: + if os.path.exists(path): + return path - Rückgabe: - dict: Geladene Daten (oder {} bei Fehler) - """ - import json - file_path = get_data_file_path(filename) - try: - with open(file_path, "r") as f: - return json.load(f) - except (FileNotFoundError, json.JSONDecodeError): - return {} - -def save_data(data, filename="data.json"): - """ - Speichert Daten in eine JSON-Datei. - - Args: - data (dict): Zu speichernde Daten - filename (str): Dateiname - - Rückgabe: - bool: True bei Erfolg - """ - import json - file_path = get_data_file_path(filename) - try: - with open(file_path, "w") as f: - json.dump(data, f, indent=2) - return True - except Exception as e: - print(f"Fehler beim Speichern: {e}") - return False - -# ============================================================================ -# BEISPIEL: EINFACHE LOGIK -# ============================================================================ - -# Zähler für Button-Klicks (Beispiel) -_click_counter = 0 - -def increment_counter(): - """Inkrementiert den Klick-Zähler.""" - global _click_counter - _click_counter += 1 - return _click_counter - -def get_counter(): - """Gibt den aktuellen Zählerstand zurück.""" - global _click_counter - return _click_counter - -# ============================================================================ -# MAIN (wird nicht automatisch ausgeführt - PyOtherSide lädt nur Funktionen) -# ============================================================================ - -# Hinweis: In PyOtherSide wird nur importiert, was in QML aufgerufen wird. -# Top-Level Code wird NICHT ausgeführt! - -# Beispiel für Initialisierung (wird erst beim ersten Aufruf ausgeführt): -_initialized = False - -def init(): - """Initialisiert das Modul (wird beim ersten Aufruf aus QML ausgeführt).""" - global _initialized - if not _initialized: - print(f"{APP_NAME} Modul initialisiert") - _initialized = True - return _initialized + return os.path.join("assets", filename)