feat: fortune cookie v1.0 - qml ui und python backend implementiert
This commit is contained in:
+4
-1
@@ -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 +0,0 @@
|
|||||||
# Platzhalter: Hier soll cookie_crack.mp3 oder cookie_crack.wav hingelegt werden
|
|
||||||
@@ -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
@@ -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"
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|||||||
+113
-79
@@ -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
|
||||||
anchors.fill: parent
|
// ================================================================
|
||||||
spacing: units.gu(2) // 2 GU Abstand zwischen Elementen
|
|
||||||
|
|
||||||
// Status-Label (optional)
|
// Cookie-Image (zentral)
|
||||||
Label {
|
Image {
|
||||||
id: statusLabel
|
id: cookieImage
|
||||||
text: "Lade..."
|
anchors.centerIn: parent
|
||||||
Layout.fillWidth: true
|
width: units.gu(30)
|
||||||
horizontalAlignment: Text.AlignHCenter
|
height: units.gu(30)
|
||||||
fontSize: "large" // ✅ NEU 1.7: String-Wert!
|
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
|
// Tap auf Cookie (wenn geschlossen)
|
||||||
// Beispiel: Textfeld
|
MouseArea {
|
||||||
Label {
|
anchors.fill: parent
|
||||||
id: contentLabel
|
hoverEnabled: true
|
||||||
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
|
|
||||||
|
|
||||||
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
@@ -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
|
||||||
|
|
||||||
|
# Lade Fortunes
|
||||||
|
_load_fortunes()
|
||||||
|
|
||||||
|
# Lade Last-State
|
||||||
|
_music_enabled = _load_music_state()
|
||||||
|
|
||||||
|
_initialized = True
|
||||||
return 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
|
|
||||||
|
|||||||
Reference in New Issue
Block a user