feat: phase 1 - einstellungen mit lautstärke und spruchlisten

- Settings.qml mit Lautstärke-Slidern für Musik und Knack-Geräusch
- Spruchlisten-Auswahl (classic, farmer_wisdom, unfortune)
- StackLayout für Navigation zwischen Hauptseite und Einstellungen
- Python: load_setting/save_setting für generische Einstellungen
- Python: Volume-Einstellungen und Spruchlisten-Verwaltung
- ASSET_REQUIREMENTS.md für Grafik-Spezifikationen
- JSON-Dateien: classic.json, farmer_wisdom.json, unfortune.json

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
darklithium
2026-06-02 21:26:41 +02:00
parent da719d7670
commit c4e7d4bd55
7 changed files with 1019 additions and 235 deletions
+156 -88
View File
@@ -1,21 +1,21 @@
"""
Fortune Cookie v1.0 - Python Backend Module
Framework 1.7 Standard
Fortune Cookie v1.1 - Python Backend Module
Framework 1.8 Standard
Audio-Steuerung in QML (keine Qt-Python-Bindings benoetigt)
Last-State Speicherung mit Datei-basiertem Speicher
Einstellungen: Lautstaerke, Spruchlisten
"""
import os
import random
import json
import platform
from pathlib import Path
# ============================================================================
# APP-METADATEN
# ============================================================================
APP_NAME = "fortunecookie"
APP_VERSION = "1.0.0"
APP_VERSION = "1.1.0"
MAINTAINER = "darklithium <dev@darklithium.de>"
@@ -32,90 +32,107 @@ def get_platform():
# ============================================================================
# FORTUNE-LOGIK
# FORTUNE-LISTEN VERWALTUNG
# ============================================================================
# Globale Variablen
# Verfuegbare Spruchlisten
AVAILABLE_FORTUNE_LISTS = [
"classic", # Standard Glückskeks-Sprueche
"farmer_wisdom", # Bauernweisheiten
"unfortune", # Gothic/UNfortune-Sprueche
]
# Aktuelle Spruchliste (Standard: classic)
_current_fortune_list = "classic"
_current_fortune = ""
_fortunes = []
_fortunes = {}
_initialized = False
def _init():
"""Initialisiert das Modul (wird beim ersten Aufruf ausgefuehrt)."""
global _fortunes, _initialized
global _fortunes, _initialized, _current_fortune_list
if _initialized:
return True
# Lade Fortunes
_load_fortunes()
# Lade alle Spruchlisten
_load_all_fortune_lists()
# Lade aktuelle Liste aus Einstellungen
_current_fortune_list = load_setting("fortune_list", default_value="classic")
if _current_fortune_list not in _fortunes:
_current_fortune_list = "classic"
_initialized = True
return True
def _load_fortunes():
"""Laedt alle Sprueche aus assets/fortunes.json."""
def _load_all_fortune_lists():
"""Laedt alle verfuegbaren Spruchlisten."""
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"),
]
base_path = os.path.join(os.path.dirname(__file__), "..", "assets", "fortunes")
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):
# 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
for list_name in AVAILABLE_FORTUNE_LISTS:
try:
json_path = os.path.join(base_path, f"{list_name}.json")
if os.path.exists(json_path):
with open(json_path, "r", encoding="utf-8") as f:
_fortunes[list_name] = json.load(f)
else:
# Fallback: Leere Liste
_fortunes[list_name] = []
except Exception as e:
print(f"WARN: Spruchliste {list_name} nicht geladen: {e}")
_fortunes[list_name] = []
except Exception as e:
print(f"ERROR: Spruchlisten nicht geladen: {e}")
# Fallback: Standard-Sprueche
_fortunes = [
"Ein guter Tag beginnt mit einem Laecheln.",
"Das Glueck liegt in den kleinen Dingen.",
"Geduld ist eine Tugend.",
]
except Exception:
_fortunes = []
_fortunes = {
"classic": [
"Ein guter Tag beginnt mit einem Laecheln.",
"Das Glueck liegt in den kleinen Dingen.",
"Geduld ist eine Tugend.",
],
"farmer_wisdom": [],
"unfortune": []
}
def _get_random_fortune():
"""Gibt einen zufaelligen Spruch zurueck."""
if not _fortunes:
_load_fortunes()
return random.choice(_fortunes) if _fortunes else "Keine Sprueche verfguebar."
"""Gibt einen zufaelligen Spruch aus der aktuellen Liste zurueck."""
global _current_fortune_list, _fortunes
_init()
if _current_fortune_list not in _fortunes or not _fortunes[_current_fortune_list]:
# Fallback auf classic
_current_fortune_list = "classic"
if not _fortunes.get("classic"):
return "Keine Sprueche verfguebar."
return random.choice(_fortunes[_current_fortune_list])
def get_initial_fortune():
"""Gibt einen zufaelligen Spruch fuer den Start zurueck."""
_init()
global _current_fortune
_init()
_current_fortune = _get_random_fortune()
return _current_fortune
def open_fortune():
"""Oeffnet den Fortune Cookie (neuer Spruch)."""
_init()
"""Oeffnet den Fortune Cookie (neuer Spruch aus aktueller Liste)."""
global _current_fortune
# Neuer Spruch
_init()
_current_fortune = _get_random_fortune()
return True
@@ -128,23 +145,16 @@ def get_current_fortune():
def get_new_fortune():
"""Gibt einen neuen Spruch zurueck (Cookie schliesst sich)."""
_init()
global _current_fortune
_init()
_current_fortune = _get_random_fortune()
return _current_fortune
# ============================================================================
# LAST-STATE SPEICHERUNG (Musik an/aus)
# EINSTELLUNGEN (Settings)
# ============================================================================
#
# Verwende diese Funktionen, um App-Zustände zwischen App-Starts zu speichern.
# WICHTIG:
# - In Click-Apps: ~/.cache/<appname>/ ist beschreibbar
# - Datei-Inhalt: Einfach "true" oder "false" als String speichern
# - Immer FRISCH aus Datei laden (kein Caching!)
# Cache-Verzeichnis für Konfigurationen
def _get_config_dir():
"""Gibt das Konfigurationsverzeichnis zurück (Click-App kompatibel)."""
try:
@@ -154,42 +164,49 @@ def _get_config_dir():
os.makedirs(cache_dir, exist_ok=True)
return cache_dir
except Exception:
# Fallback für Tests
return os.path.join("/tmp", APP_NAME + "_config")
def _get_state_file(state_name):
"""Gibt den Pfad zu einer Zustandsdatei zurück."""
return os.path.join(_get_config_dir(), state_name)
def _get_setting_file(setting_name):
"""Gibt den Pfad zu einer Einstellungsdatei zurück."""
return os.path.join(_get_config_dir(), f"{setting_name}.txt")
def load_state(state_name, default_value=True):
"""Lädt einen Zustand aus einer Datei.
def load_setting(setting_name, default_value=None):
"""Laedt eine Einstellung aus einer Datei.
Args:
state_name (str): Name des Zustands
default_value (bool): Standardwert, wenn Datei nicht existiert
setting_name (str): Name der Einstellung
default_value: Standardwert, wenn Datei nicht existiert
Rückgabe:
bool: Der geladene Zustand (True/False)
Wert der Einstellung (Typ hängt von default_value ab)
"""
try:
state_file = _get_state_file(state_name)
if os.path.exists(state_file):
with open(state_file, "r") as f:
content = f.read().strip().lower()
return content == "true"
setting_file = _get_setting_file(setting_name)
if os.path.exists(setting_file):
with open(setting_file, "r") as f:
content = f.read().strip()
# Try to convert to appropriate type
if default_value is not None:
if isinstance(default_value, bool):
return content.lower() == "true"
elif isinstance(default_value, int):
return int(content)
elif isinstance(default_value, float):
return float(content)
return content
except Exception as e:
print(f"WARN: Zustand nicht geladen ({state_name}): {e}")
print(f"WARN: Einstellung nicht geladen ({setting_name}): {e}")
return default_value
def save_state(state_name, value):
"""Speichert einen Zustand in einer Datei.
def save_setting(setting_name, value):
"""Speichert eine Einstellung in einer Datei.
Args:
state_name (str): Name des Zustands
value (bool): Der zu speichernde Zustand (True/False)
setting_name (str): Name der Einstellung
value: Zu speichernder Wert (wird zu String konvertiert)
Rückgabe:
bool: True bei Erfolg
@@ -197,33 +214,84 @@ def save_state(state_name, value):
try:
config_dir = _get_config_dir()
os.makedirs(config_dir, exist_ok=True)
state_file = _get_state_file(state_name)
with open(state_file, "w") as f:
f.write("true" if value else "false")
setting_file = _get_setting_file(setting_name)
with open(setting_file, "w") as f:
f.write(str(value))
return True
except Exception as e:
print(f"WARN: Zustand nicht gespeichert ({state_name}): {e}")
print(f"WARN: Einstellung nicht gespeichert ({setting_name}): {e}")
return False
def get_music_enabled():
"""Gibt zurück, ob Musik aktiviert ist (Last-State).
# ============================================================================
# LAUTSTÄRKE-EINSTELLUNGEN
# ============================================================================
Lädt den Zustand FRISCH aus der Datei bei jedem Aufruf.
"""
return load_state("music_enabled", default_value=True)
def get_music_volume():
"""Gibt die Musik-Lautstärke zurück (0.0 - 1.0)."""
return float(load_setting("music_volume", default_value=0.5))
def set_music_enabled(enabled):
"""Setzt den Musik-Status und speichert ihn persistent.
def set_music_volume(volume):
"""Setzt die Musik-Lautstärke (0.0 - 1.0)."""
# Clamp value between 0.0 and 1.0
volume = max(0.0, min(1.0, float(volume)))
return save_setting("music_volume", volume)
def get_crack_volume():
"""Gibt die Knack-Lautstärke zurück (0.0 - 1.0)."""
return float(load_setting("crack_volume", default_value=1.0))
def set_crack_volume(volume):
"""Setzt die Knack-Lautstärke (0.0 - 1.0)."""
volume = max(0.0, min(1.0, float(volume)))
return save_setting("crack_volume", volume)
# ============================================================================
# SPRUCHLISTEN-EINSTELLUNGEN
# ============================================================================
def get_fortune_lists():
"""Gibt die Liste der verfuegbaren Spruchlisten zurück."""
_init()
return AVAILABLE_FORTUNE_LISTS
def get_current_fortune_list():
"""Gibt den Namen der aktuellen Spruchliste zurück."""
_init()
return load_setting("fortune_list", default_value="classic")
def set_fortune_list(list_name):
"""Setzt die aktuelle Spruchliste.
Args:
enabled (bool): True = Musik an, False = Musik aus
list_name (str): Name der Spruchliste (classic, farmer_wisdom, unfortune)
Rückgabe:
bool: True bei Erfolg
"""
return save_state("music_enabled", enabled)
if list_name not in AVAILABLE_FORTUNE_LISTS:
return False
return save_setting("fortune_list", list_name)
# ============================================================================
# MUSIK EIN/AUS (Last-State)
# ============================================================================
def get_music_enabled():
"""Gibt zurück, ob Musik aktiviert ist (Last-State)."""
return load_setting("music_enabled", default_value=True)
def set_music_enabled(enabled):
"""Setzt den Musik-Status und speichert ihn persistent."""
return save_setting("music_enabled", bool(enabled))
# ============================================================================