feat: fortune cookie v1.0 - qml ui und python backend implementiert

This commit is contained in:
darklithium
2026-06-01 21:16:26 +02:00
parent 9123c7465f
commit 114ccc6c4f
6 changed files with 397 additions and 307 deletions
+4 -1
View File
@@ -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 ${CMAKE_CURRENT_BINARY_DIR}/manifest.json DESTINATION ${CMAKE_INSTALL_PREFIX})
install(FILES ${PROJECT_NAME}.apparmor DESTINATION ${DATA_DIR}) 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 # Dateien installieren
install(DIRECTORY assets DESTINATION ${DATA_DIR}) install(DIRECTORY assets DESTINATION ${DATA_DIR})
install(DIRECTORY src DESTINATION ${DATA_DIR}) install(DIRECTORY src DESTINATION ${DATA_DIR})
install(DIRECTORY qml 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/) # OPTIONAL: Für Apps mit Übersetzungen (po/)
-1
View File
@@ -1 +0,0 @@
# Platzhalter: Hier soll cookie_crack.mp3 oder cookie_crack.wav hingelegt werden
-32
View File
@@ -9,35 +9,3 @@ Terminal=false
Categories=Utility; Categories=Utility;
X-Lomiri-Touch=true X-Lomiri-Touch=true
X-Ubuntu-Applications=fortunecookie 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
*/
+2 -40
View File
@@ -1,9 +1,9 @@
{ {
"name": "fortunecookie.darklithium", "name": "fortunecookie.darklithium",
"title": "Fortune Cookie", "title": "Fortune Cookie",
"version": "0.1.0", "version": "1.0.0",
"description": "Glückskeks App mit Sprüchen, Musik und einfacher Listenverwaltung", "description": "Glückskeks App mit Sprüchen, Musik und einfacher Listenverwaltung",
"maintainer": "Christian Franz <dev@darklithium.de>", "maintainer": "darklithium <dev@darklithium.de>",
"architecture": "all", "architecture": "all",
"framework": "ubuntu-sdk-20.04", "framework": "ubuntu-sdk-20.04",
"hooks": { "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 <email>" (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 <dev@darklithium.de>",
* "architecture": "all",
* "framework": "ubuntu-sdk-20.04",
* "hooks": {
* "meine-app": {
* "apparmor": "meine-app.apparmor",
* "desktop": "meine-app.desktop"
* }
* }
* }
*/
+110 -76
View File
@@ -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 '<app-name>' 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 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 1.3
import Lomiri.Components.Popups 1.3 import Lomiri.Components.Popups 1.3
import io.thp.pyotherside 1.4 import io.thp.pyotherside 1.4
MainView { MainView {
id: root id: root
objectName: 'mainView' applicationName: "fortunecookie.darklithium"
// APP META-DATEN (Anpassen!)
applicationName: '<app-name>.darklithium'
width: units.gu(45) width: units.gu(45)
height: units.gu(75) height: units.gu(75)
theme.name: "Lomiri.Components.Themes.SuruDark" theme.name: "Lomiri.Components.Themes.SuruDark"
// ==================================================================== // ====================================================================
// PYTHON-MODUL (STANDARD 1.7) // 1. PYTHON-MODUL (STANDARD 1.7)
// ==================================================================== // ====================================================================
Python { Python {
id: py id: py
Component.onCompleted: { Component.onCompleted: {
// ✅ STANDARD 1.7: Qt.resolvedUrl funktioniert!
addImportPath(Qt.resolvedUrl("../src")); addImportPath(Qt.resolvedUrl("../src"));
importModule("fortunecookie", function() {
importModule('<app-name>', function() { console.log("Python-Modul fortunecookie geladen");
console.log("Python-Modul <app-name> geladen"); // Initialisierung
// Initialisierung nach erfolgreicher Modul-Ladung currentFortuneLabel.text = py.call_sync("fortunecookie.get_initial_fortune", []);
statusLabel.text = py.call_sync("<app-name>.get_status_text", []); cookieImage.source = "assets/cookie_closed.png";
footerLabel.text = py.call_sync("<app-name>.get_platform", []); 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 { Page {
id: mainPage id: mainPage
anchors.fill: parent anchors.fill: parent
// Header // Header
header: PageHeader { header: PageHeader {
title: "<App-Name>" // App-Name anpassen title: "Fortune Cookie"
} }
// Hauptinhalt (ColumnLayout für vertikale Anordnung) // ================================================================
ColumnLayout { // COOKIE & SPRUCH
// ================================================================
// 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 anchors.fill: parent
spacing: units.gu(2) // 2 GU Abstand zwischen Elementen property real startY: 0
// Status-Label (optional) onPressed: {
Label { startY = mouseY
id: statusLabel
text: "Lade..."
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
fontSize: "large" // ✅ NEU 1.7: String-Wert!
} }
// Hauptinhalt hier einfügen onReleased: {
// Beispiel: Textfeld // Ende der Geste - pruufen ob nach oben gewischt
Label { if (mouseY < startY - units.gu(2)) {
id: contentLabel // Wisch nach oben -> Cookie oeffnen
text: "Hier steht Beispieltext" py.call("fortunecookie.open_fortune", [], function() {
Layout.fillWidth: true fortuneOpened = true;
horizontalAlignment: Text.AlignHCenter currentFortune = py.call_sync("fortunecookie.get_current_fortune", []);
fontSize: "x-large" // ✅ NEU 1.7: String-Wert! currentFortuneLabel.text = currentFortune;
cookieImage.source = "assets/cookie_open.png";
});
}
}
} }
// Beispiel: Button // Tap auf Cookie (wenn geschlossen)
Button { MouseArea {
id: testButton anchors.fill: parent
text: "Test Button" hoverEnabled: true
Layout.fillWidth: false
Layout.preferredWidth: units.gu(20) // 200 DP (Touch-optimiert!)
Layout.preferredHeight: units.gu(8) // 80 DP
onClicked: { onClicked: {
if (py) { if (!fortuneOpened) {
py.call("<app-name>.on_button_click", [], function() { // Cookie oeffnen (gleiche Funktion wie Wisch nach oben)
contentLabel.text = py.call_sync("<app-name>.get_content_text", []); 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";
}); });
} }
} }
} }
} }
// ================================================================ // Fortune-Text (erscheint nach dem Oeffnen)
// FOOTER (Workaround für Lomiri.Components 1.3)
// ================================================================
// ⚠️ WICHTIG: PageFooter existiert NICHT in Lomiri.Components 1.3!
Label { Label {
id: footerLabel id: currentFortuneLabel
anchors.bottom: parent.bottom anchors {
anchors.left: parent.left top: cookieImage.bottom
anchors.right: parent.right topMargin: units.gu(2)
anchors.bottomMargin: units.gu(2) left: parent.left
text: "Plattform: ?" right: parent.right
fontSize: "medium" // ✅ NEU 1.7: String-Wert! leftMargin: units.gu(2)
rightMargin: units.gu(2)
}
text: ""
fontSize: "large"
horizontalAlignment: Text.AlignHCenter 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;
}
} }
} }
} }
+278 -154
View File
@@ -1,194 +1,318 @@
""" """
UNIVERSELLES PYTHON-MODUL TEMPLATE für Ubuntu Touch Apps Fortune Cookie v1.0 - Python Backend Module
Basierend auf metime und Referenz-App (Version 1.7) Framework 1.7 Standard
VERWENDUNG:
1. Kopiere diese Datei nach src/<app-name>.py
2. Ersetze '<app-name>' 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)
""" """
import os import os
import random
import json
import platform import platform
from pathlib import Path
# ============================================================================ # ============================================================================
# APP-METADATEN (Anpassen!) # APP-METADATEN
# ============================================================================ # ============================================================================
APP_NAME = "fortunecookie"
APP_NAME = "<app-name>" # App-Name (z. B. "meine-app") APP_VERSION = "1.0.0"
APP_VERSION = "0.1.0" # Version (Semantic Versioning) MAINTAINER = "darklithium <dev@darklithium.de>"
MAINTAINER = "Christian Franz <dev@darklithium.de>" # Maintainer
# ============================================================================ # ============================================================================
# PLATTFORM-ERKENNUNG (1.7 Standard) # PLATTFORM-ERKENNUNG (Framework 1.7 Standard)
# ============================================================================ # ============================================================================
def get_platform(): def get_platform():
""" """Gibt die aktuelle Plattform zurueck (arm64/amd64)."""
Gibt die aktuelle Plattform zurück (arm64/amd64).
Rückgabe:
str: "arm64" oder "amd64"
"""
machine = platform.machine().lower() machine = platform.machine().lower()
if "arm" in machine or "aarch" in machine: if "arm" in machine or "aarch" in machine:
return "arm64" return "arm64"
return "amd64" 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(): # Globale Variablen
""" _current_fortune = ""
Gibt den aktuellen Inhaltstext zurück. _fortunes = []
_initialized = False
Rückgabe: # Musik-Status
str: Text für die UI _music_enabled = True
""" _music_playing = False
return "Button wurde geklickt! (Python → QML)"
def on_button_click(): # Medien-Player (wird lazy initialisiert)
""" _media_player = None
Wird aufgerufen, wenn der Button in QML geklickt wird. _cookie_crack_sound = None
Rückgabe:
bool: True bei Erfolg def _init():
""" """Initialisiert das Modul (wird beim ersten Aufruf ausgefuehrt)."""
print("Button clicked") # Log für Debugging global _fortunes, _initialized, _music_enabled
if _initialized:
return True 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(): def get_data_dir():
""" """Gibt das Datenverzeichnis der App zurueck."""
Gibt das Datenverzeichnis der App zurück. 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): def get_fortunes_file_path():
""" """Gibt den Pfad zur Fortunes-Datei zurueck."""
Gibt den Pfad zu einer Daten-Datei zurück. 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"): def get_asset_path(filename):
""" """Gibt den Pfad zu einer Asset-Datei zurueck (funktioniert in Click & Desktop)."""
Lädt JSON-Daten aus einer Datei. possible_paths = [
os.path.join(os.path.dirname(__file__), "..", "assets", filename),
os.path.join("assets", filename),
]
Args: for path in possible_paths:
filename (str): Dateiname if os.path.exists(path):
return path
Rückgabe: return os.path.join("assets", filename)
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