EFEK SUARA PADA SPLASH SCREEN

 EFEK SUARA PADA SPLASH SCREEN


1. Efek suara (bip atau file suara .wav/.mp3)

Berikut ini  efek suara (file suara .wav/.mp3)  saat splash selesai → biar makin dramatis. 🎡

Kita bisa tambahkan efek suara saat splash selesai (ketika fade-out → sebelum masuk menu utama).
Di Python (Tkinter), opsi simpel:

  • Windows/Linux → pakai playsound (pip install playsound)
  • Android (Pydroid3) → biasanya lebih aman pakai pygame (pip install pygame) karena playsound suka error di Android.

Aku bikinkan contoh dengan pygame.mixer (lebih fleksibel, bisa MP3/WAV)


πŸ“Œ splash_screen.py (Rainbow Progress + Rainbow Text + Dot Animation + Efek Suara)

import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk  # pip install pillow
import pygame  # pip install pygame
from menu_utama import tampilkan_halaman_utama

# Path logo (ubah sesuai lokasi logo kamu)
LOGO_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/assets/logo.png"

# Path sound effect (mp3 atau wav)
SOUND_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/assets/splash_sound.mp3"

def show_splash_screen():
    splash = tk.Tk()
    splash.title("SmartNutriChefAI - Splash")
    splash.geometry("520x400")
    splash.configure(bg="white")
    splash.resizable(False, False)

    # Transparansi awal (fade-in start)
    splash.attributes("-alpha", 0.0)

    # === Logo ===
    try:
        img = Image.open(LOGO_PATH)
        img = img.resize((150, 150), Image.LANCZOS)
        logo_img = ImageTk.PhotoImage(img)
        logo_label = tk.Label(splash, image=logo_img, bg="white")
        logo_label.image = logo_img
        logo_label.pack(pady=25)
    except Exception as e:
        tk.Label(splash, text=f"[Logo not found]\n{e}",
                 font=("Arial", 10), fg="red", bg="white").pack(pady=25)

    # === Judul ===
    tk.Label(
        splash, text="SmartNutriChefAI",
        font=("Arial", 22, "bold"), fg="#4CAF50", bg="white"
    ).pack(pady=5)

    tk.Label(
        splash, text="by SR Pakpahan",
        font=("Arial", 12), fg="gray", bg="white"
    ).pack(pady=5)

    # === Label Loading (rainbow + animasi dot) ===
    progress_label = tk.Label(
        splash, text="Loading.",
        font=("Arial", 12, "bold"), fg="black", bg="white"
    )
    progress_label.pack(pady=10)

    # === Progress bar rainbow ===
    style = ttk.Style()
    style.theme_use("clam")

    style.configure("Rainbow.Horizontal.TProgressbar",
                    troughcolor="white",
                    bordercolor="white",
                    background="red")

    progress = ttk.Progressbar(
        splash, orient="horizontal",
        length=300, mode="indeterminate",
        style="Rainbow.Horizontal.TProgressbar"
    )
    progress.pack(pady=20)

    # === Rainbow warna loop ===
    rainbow_colors = ["#FF0000", "#FF7F00", "#FFFF00",
                      "#00FF00", "#0000FF", "#4B0082", "#8B00FF"]
    color_index = {"i": 0}

    # Animasi dot "Loading."
    dots = [".", "..", "..."]
    dot_index = {"i": 0}

    def cycle_colors_and_text():
        # Ganti warna
        color = rainbow_colors[color_index["i"] % len(rainbow_colors)]
        style.configure("Rainbow.Horizontal.TProgressbar",
                        background=color, lightcolor=color, darkcolor=color)
        # Ganti teks + warna
        progress_label.config(
            text="Loading" + dots[dot_index["i"] % len(dots)],
            fg=color
        )
        # Increment index
        color_index["i"] += 1
        dot_index["i"] += 1
        splash.after(400, cycle_colors_and_text)  # update tiap 400ms

    cycle_colors_and_text()

    # === Fade-in animasi ===
    def fade_in(opacity=0.0):
        if opacity <= 1.0:
            splash.attributes("-alpha", opacity)
            splash.after(40, lambda: fade_in(opacity + 0.05))

    fade_in()

    # Jalankan progress bar indeterminate (animasi bergelombang)
    progress.start(10)

    # Setelah beberapa detik → stop & fade-out
    splash.after(6000, lambda: fade_out())  # tampil 6 detik

    # === Fade-out animasi + efek suara ===
    def fade_out(opacity=1.0):
        if opacity > 0.0:
            splash.attributes("-alpha", opacity)
            splash.after(40, lambda: fade_out(opacity - 0.05))
        else:
            progress.stop()
            play_sound_effect()  # πŸ”Š mainkan suara
            open_main_menu(splash)

    splash.mainloop()


def play_sound_effect():
    """Mainkan efek suara saat splash selesai"""
    try:
        pygame.mixer.init()
        pygame.mixer.music.load(SOUND_PATH)
        pygame.mixer.music.play()
    except Exception as e:
        print(f"[Sound error] {e}")


def open_main_menu(splash_root):
    splash_root.destroy()
    root = tk.Tk()
    root.title("SmartNutriChefAI")
    root.geometry("600x500")
    tampilkan_halaman_utama(root)
    root.mainloop()


if __name__ == "__main__":
    show_splash_screen()

✨ Hasil tambahan:

  1. Begitu splash selesai → efek suara otomatis dimainkan 🎢
  2. Kamu bisa pakai .mp3 (misalnya suara “ding” / “welcome”) atau .wav (lebih ringan).
  3. Efek ini berjalan bersamaan dengan transisi fade-out → jadi lebih dramatis πŸ”₯


2.  opsi toggle di settings  

Berikut juga opsi toggle di settings (misalnya user bisa pilih: Sound ON / OFF) supaya bisa dimatikan kalau tidak mau ada suara. πŸŽ›️

kita tambahkan opsi Settings → Sound ON/OFF supaya user bisa atur sendiri apakah efek suara splash dipakai atau tidak πŸŽ›️.
Cara gampang: kita simpan flag pengaturan di file settings.json (atau SQLite, tapi JSON lebih ringan).


πŸ“Œ settings_manager.py

import os, json

SETTINGS_FILE = "/storage/emulated/0/AppJadi/SmartNutriChefAI/settings.json"

DEFAULT_SETTINGS = {
    "sound_enabled": True
}

def load_settings():
    if not os.path.exists(SETTINGS_FILE):
        save_settings(DEFAULT_SETTINGS)
        return DEFAULT_SETTINGS
    with open(SETTINGS_FILE, "r") as f:
        return json.load(f)

def save_settings(settings):
    with open(SETTINGS_FILE, "w") as f:
        json.dump(settings, f, indent=4)

πŸ“Œ Modifikasi splash_screen.py

Integrasikan setting sound:

import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
import pygame
from menu_utama import tampilkan_halaman_utama
from settings_manager import load_settings  # πŸ”§

LOGO_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/assets/logo.png"
SOUND_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/assets/splash_sound.mp3"

def show_splash_screen():
    splash = tk.Tk()
    splash.title("SmartNutriChefAI - Splash")
    splash.geometry("520x400")
    splash.configure(bg="white")
    splash.resizable(False, False)
    splash.attributes("-alpha", 0.0)

    # === Logo ===
    try:
        img = Image.open(LOGO_PATH)
        img = img.resize((150, 150), Image.LANCZOS)
        logo_img = ImageTk.PhotoImage(img)
        logo_label = tk.Label(splash, image=logo_img, bg="white")
        logo_label.image = logo_img
        logo_label.pack(pady=25)
    except Exception as e:
        tk.Label(splash, text=f"[Logo not found]\n{e}",
                 font=("Arial", 10), fg="red", bg="white").pack(pady=25)

    tk.Label(splash, text="SmartNutriChefAI",
             font=("Arial", 22, "bold"), fg="#4CAF50", bg="white").pack(pady=5)

    tk.Label(splash, text="by SR Pakpahan",
             font=("Arial", 12), fg="gray", bg="white").pack(pady=5)

    # === Label Loading rainbow ===
    progress_label = tk.Label(splash, text="Loading.",
                              font=("Arial", 12, "bold"), fg="black", bg="white")
    progress_label.pack(pady=10)

    # === Progressbar ===
    style = ttk.Style()
    style.theme_use("clam")
    style.configure("Rainbow.Horizontal.TProgressbar",
                    troughcolor="white",
                    bordercolor="white",
                    background="red")

    progress = ttk.Progressbar(splash, orient="horizontal",
                               length=300, mode="indeterminate",
                               style="Rainbow.Horizontal.TProgressbar")
    progress.pack(pady=20)

    rainbow_colors = ["#FF0000", "#FF7F00", "#FFFF00",
                      "#00FF00", "#0000FF", "#4B0082", "#8B00FF"]
    color_index = {"i": 0}
    dots = [".", "..", "..."]
    dot_index = {"i": 0}

    def cycle_colors_and_text():
        color = rainbow_colors[color_index["i"] % len(rainbow_colors)]
        style.configure("Rainbow.Horizontal.TProgressbar",
                        background=color, lightcolor=color, darkcolor=color)
        progress_label.config(text="Loading" + dots[dot_index["i"] % len(dots)], fg=color)
        color_index["i"] += 1
        dot_index["i"] += 1
        splash.after(400, cycle_colors_and_text)

    cycle_colors_and_text()

    def fade_in(opacity=0.0):
        if opacity <= 1.0:
            splash.attributes("-alpha", opacity)
            splash.after(40, lambda: fade_in(opacity + 0.05))
    fade_in()

    progress.start(10)
    splash.after(6000, lambda: fade_out())

    def fade_out(opacity=1.0):
        if opacity > 0.0:
            splash.attributes("-alpha", opacity)
            splash.after(40, lambda: fade_out(opacity - 0.05))
        else:
            progress.stop()
            settings = load_settings()
            if settings.get("sound_enabled", True):
                play_sound_effect()  # πŸ”Š hanya kalau ON
            open_main_menu(splash)

    splash.mainloop()

def play_sound_effect():
    try:
        pygame.mixer.init()
        pygame.mixer.music.load(SOUND_PATH)
        pygame.mixer.music.play()
    except Exception as e:
        print(f"[Sound error] {e}")

def open_main_menu(splash_root):
    splash_root.destroy()
    root = tk.Tk()
    root.title("SmartNutriChefAI")
    root.geometry("600x500")
    tampilkan_halaman_utama(root)
    root.mainloop()

if __name__ == "__main__":
    show_splash_screen()

πŸ“Œ Tambah Menu Settings (opsional di menu_utama.py)

from settings_manager import load_settings, save_settings

def open_settings(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="Pengaturan", font=("Arial", 16, "bold")).pack(pady=20)

    settings = load_settings()
    sound_var = tk.BooleanVar(value=settings.get("sound_enabled", True))

    tk.Checkbutton(root_frame, text="Aktifkan Suara Splash",
                   variable=sound_var, font=("Arial", 12)).pack(pady=10)

    def simpan():
        settings["sound_enabled"] = sound_var.get()
        save_settings(settings)
        messagebox.showinfo("Info", "Pengaturan tersimpan!")

    tk.Button(root_frame, text="Simpan", command=simpan,
              bg="green", fg="white").pack(pady=10)

    tk.Button(root_frame, text="⬅ Kembali", bg="gray", fg="white",
              command=lambda: tampilkan_halaman_utama(root_frame)).pack(pady=20)

⚡ Jadi:

  • Default → suara aktif
  • Bisa dimatikan di menu settings
  • Splash screen cek setting dulu sebelum mainkan musik

Mau saya sekalian bikinkan UI tombol Settings langsung di menu_utama.py biar bisa kamu akses cepat dari menu utama?



3. Pengaturan Tombol UI 

Berikut UI tombol Settings langsung di menu_utama.py biar bisa kamu akses cepat dari menu utama?

Biar alurnya rapi saya tambahkan tombol ⚙ Settings langsung di menu_utama.py.
Tombol ini nanti bisa dipakai untuk mengatur opsi (misalnya Sound ON/OFF untuk splash, tema warna, dsb).

Berikut versi terbaru menu_utama.py dengan tambahan tombol Settings:

# menu_utama.py
import tkinter as tk
from menu_metode_formula_mmag import show_menu_metode_formula_mmag

def tampilkan_halaman_utama(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="SmartNutriChefAI", font=("Arial", 22, "bold")).pack(pady=25)

    # Frame untuk tombol menu utama
    btn_frame = tk.Frame(root_frame)
    btn_frame.pack(pady=10)

    # Daftar tombol utama
    menu_items = [
        ("Metode Formula MMAG", lambda: show_menu_metode_formula_mmag(root_frame)),
        ("Tentang Aplikasi", lambda: show_tentang(root_frame)),
    ]

    for text, cmd in menu_items:
        tk.Button(
            btn_frame, text=text, font=("Arial", 14),
            width=26, height=2, bg="#4CAF50", fg="white",
            command=cmd
        ).pack(pady=10)

    # Tombol Settings (letak bawah)
    tk.Button(
        root_frame, text="⚙ Settings", font=("Arial", 12),
        width=20, height=2, bg="#2196F3", fg="white",
        command=lambda: show_settings(root_frame)
    ).pack(pady=25)

def show_tentang(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="Tentang Aplikasi", font=("Arial", 18, "bold")).pack(pady=20)
    tk.Label(root_frame, text="SmartNutriChefAI\nVersi 1.0\n\nDibuat untuk membantu manajemen bahan & resep.",
             font=("Arial", 12), justify="center").pack(pady=20)

    tk.Button(
        root_frame, text="⬅ Kembali", font=("Arial", 12),
        width=20, height=2, bg="gray", fg="white",
        command=lambda: tampilkan_halaman_utama(root_frame)
    ).pack(pady=20)

# === UI Settings sederhana ===
def show_settings(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="Settings", font=("Arial", 18, "bold")).pack(pady=20)

    # Contoh toggle Sound ON/OFF
    sound_var = tk.BooleanVar(value=True)  # default ON
    def toggle_sound():
        state = "ON" if sound_var.get() else "OFF"
        tk.messagebox.showinfo("Pengaturan", f"Sound sekarang: {state}")

    tk.Checkbutton(
        root_frame, text="Sound ON/OFF", variable=sound_var,
        font=("Arial", 12), command=toggle_sound
    ).pack(pady=10)

    # Tombol kembali
    tk.Button(
        root_frame, text="⬅ Kembali", font=("Arial", 12),
        width=20, height=2, bg="gray", fg="white",
        command=lambda: tampilkan_halaman_utama(root_frame)
    ).pack(pady=20)

πŸ“Œ Perubahan utama:

  • Ada tombol ⚙ Settings di menu utama.
  • Halaman Settings sudah ada contoh toggle Sound ON/OFF (bisa kamu hubungkan ke splash screen).
  • Struktur tetap konsisten dengan halaman lain.


4. Penyimpanan pilihan Settings ke file JSON

Berikut   penyimpanan pilihan Settings ke file JSON  (upaya pengguna jika memilih Sound OFF, settingnya tersimpan saat buka ulang aplikasi).

Kita buat  settings.json  untuk menyimpan preferensi pengguna (misalnya Sound ON/OFF).

Jadi ketika aplikasi dibuka lagi, pengaturan status akan otomatis terbaca dari file.

Berikut  versi terbarumenu_utama.py  dengan  penyimpanan Pengaturan JSON  :

# menu_utama.py
import tkinter as tk
from tkinter import messagebox
import json, os

from menu_metode_formula_mmag import show_menu_metode_formula_mmag

# Path file settings.json
SETTINGS_FILE = "/storage/emulated/0/AppJadi/SmartNutriChefAI/settings.json"

# === Helper Settings ===
def load_settings():
    if not os.path.exists(SETTINGS_FILE):
        return {"sound": True}  # default sound ON
    try:
        with open(SETTINGS_FILE, "r") as f:
            return json.load(f)
    except Exception:
        return {"sound": True}

def save_settings(settings):
    try:
        os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
        with open(SETTINGS_FILE, "w") as f:
            json.dump(settings, f, indent=4)
    except Exception as e:
        messagebox.showerror("Error", f"Gagal simpan settings: {e}")

# === Menu Utama ===
def tampilkan_halaman_utama(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="SmartNutriChefAI", font=("Arial", 22, "bold")).pack(pady=25)

    # Frame untuk tombol menu utama
    btn_frame = tk.Frame(root_frame)
    btn_frame.pack(pady=10)

    menu_items = [
        ("Metode Formula MMAG", lambda: show_menu_metode_formula_mmag(root_frame)),
        ("Tentang Aplikasi", lambda: show_tentang(root_frame)),
    ]

    for text, cmd in menu_items:
        tk.Button(
            btn_frame, text=text, font=("Arial", 14),
            width=26, height=2, bg="#4CAF50", fg="white",
            command=cmd
        ).pack(pady=10)

    # Tombol Settings
    tk.Button(
        root_frame, text="⚙ Settings", font=("Arial", 12),
        width=20, height=2, bg="#2196F3", fg="white",
        command=lambda: show_settings(root_frame)
    ).pack(pady=25)

def show_tentang(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="Tentang Aplikasi", font=("Arial", 18, "bold")).pack(pady=20)
    tk.Label(root_frame, text="SmartNutriChefAI\nVersi 1.0\n\nDibuat untuk membantu manajemen bahan & resep.",
             font=("Arial", 12), justify="center").pack(pady=20)

    tk.Button(
        root_frame, text="⬅ Kembali", font=("Arial", 12),
        width=20, height=2, bg="gray", fg="white",
        command=lambda: tampilkan_halaman_utama(root_frame)
    ).pack(pady=20)

# === UI Settings dengan JSON ===
def show_settings(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="Settings", font=("Arial", 18, "bold")).pack(pady=20)

    # Load settings dari file
    settings = load_settings()
    sound_var = tk.BooleanVar(value=settings.get("sound", True))

    def toggle_sound():
        settings["sound"] = sound_var.get()
        save_settings(settings)
        state = "ON" if sound_var.get() else "OFF"
        messagebox.showinfo("Pengaturan", f"Sound sekarang: {state}")

    tk.Checkbutton(
        root_frame, text="Sound ON/OFF", variable=sound_var,
        font=("Arial", 12), command=toggle_sound
    ).pack(pady=10)

    # Tombol kembali
    tk.Button(
        root_frame, text="⬅ Kembali", font=("Arial", 12),
        width=20, height=2, bg="gray", fg="white",
        command=lambda: tampilkan_halaman_utama(root_frame)
    ).pack(pady=20)

πŸ“Œ Fitur baru:

  • Pengaturan pengguna (Sound ON/OFF)  tersimpansettings.json  di folder app.
  • Kalau file belum ada, otomatis dibuat dengan default  sound: true.
  • Saat splash screen atau fitur lain memerlukan pengaturan, cukup panggilan  load_settings()untuk memeriksa preferensi.


5. contoh integrasi Sound ON/OFF ini ke splash_screen.py 

Berikut sekalian buatkan  contoh integrasi Sound ON/OFF ini ke splash_screen.py  → jadi kalau  sound: false, efek suara splash tidak diputar? πŸ”ŠπŸ‘‰πŸ”‡

kita integrasikan  settings.json  (Sound ON/OFF) ke  splash_screen.py  supaya saat splash selesai, efek suara baru diputar kalau  sound: true.

Berikut  versi terbaru splash_screen.py  dengan integrasi:

# splash_screen.py
import tkinter as tk
from tkinter import ttk
import os, json
from PIL import Image, ImageTk
import threading, time
import pygame  # untuk play sound

from menu_utama import tampilkan_halaman_utama

SETTINGS_FILE = "/storage/emulated/0/AppJadi/SmartNutriChefAI/settings.json"

# --- Helper Settings ---
def load_settings():
    if not os.path.exists(SETTINGS_FILE):
        return {"sound": True}
    try:
        with open(SETTINGS_FILE, "r") as f:
            return json.load(f)
    except:
        return {"sound": True}

# --- Splash Screen ---
def show_splash(root):
    splash = tk.Toplevel(root)
    splash.overrideredirect(True)
    splash.geometry("500x350+400+200")
    splash.configure(bg="black")
    splash.attributes("-alpha", 0.0)

    # Logo
    try:
        logo = Image.open("/storage/emulated/0/AppJadi/SmartNutriChefAI/assets/logo.png")
        logo = logo.resize((160, 160))
        logo_img = ImageTk.PhotoImage(logo)
        tk.Label(splash, image=logo_img, bg="black").pack(pady=20)
        splash.logo_img = logo_img
    except:
        tk.Label(splash, text="SmartNutriChefAI", font=("Arial", 22, "bold"), fg="white", bg="black").pack(pady=20)

    # Label Loading
    loading_label = tk.Label(splash, text="Loading", font=("Arial", 16), fg="white", bg="black")
    loading_label.pack(pady=15)

    # Progress bar rainbow
    style = ttk.Style()
    style.theme_use("clam")
    style.configure("Rainbow.Horizontal.TProgressbar", troughcolor="black", thickness=20)

    pb = ttk.Progressbar(splash, mode="indeterminate", length=300, style="Rainbow.Horizontal.TProgressbar")
    pb.pack(pady=15)
    pb.start(15)

    # --- Animasi teks "Loading." "Loading.." "Loading..." ---
    def animate_dots():
        dots = ["Loading", "Loading.", "Loading..", "Loading..."]
        i = 0
        while True:
            time.sleep(0.5)
            i = (i + 1) % len(dots)
            try:
                loading_label.config(text=dots[i], fg=rainbow_colors[i % len(rainbow_colors)])
            except:
                break

    rainbow_colors = ["red", "orange", "yellow", "green", "cyan", "blue", "magenta"]
    threading.Thread(target=animate_dots, daemon=True).start()

    # --- Fade-in efek ---
    def fade_in():
        alpha = splash.attributes("-alpha")
        if alpha < 1.0:
            alpha += 0.05
            splash.attributes("-alpha", alpha)
            splash.after(50, fade_in)
    fade_in()

    # --- Close splash (dengan fade-out) ---
    def close_splash():
        def fade_out():
            alpha = splash.attributes("-alpha")
            if alpha > 0:
                alpha -= 0.05
                splash.attributes("-alpha", alpha)
                splash.after(50, fade_out)
            else:
                splash.destroy()
                tampilkan_halaman_utama(root)

                # πŸ”Š Play sound kalau setting sound: true
                settings = load_settings()
                if settings.get("sound", True):
                    try:
                        pygame.mixer.init()
                        pygame.mixer.music.load("/storage/emulated/0/AppJadi/SmartNutriChefAI/assets/splash_end.mp3")
                        pygame.mixer.music.play()
                    except Exception as e:
                        print("Sound error:", e)

        fade_out()

    splash.after(4000, close_splash)  # splash 4 detik


✨ Perubahan penting:

  1. load_settings()dipakai untuk cek preferensi  sound.
  2. Kalau begitu  sound = false, suara splash  tidak diputar  .
  3. File suara (  splash_end.mp3) bisa kamu taruh di folder  assets/.
  4. Efek fade-in + fade-out + progres bar pelangi + teks animasi tetap ada.


6. opsi pengaturan lokasi file sound (custom path di Settings)

Berikut juga  opsi pengaturan lokasi file sound (custom path di Settings)  → jadi pengguna bisa ganti file suara splash sesuai selera mereka? 🎢

Oke mantap πŸš€ kita tambahkan opsi  custom sound path  ke  settings.json→ jadi pengguna bebas ganti file suara splash (misalnya pilih musik sendiri).

Berarti  settings.jsonsekarang Struktur bisa jadi:

{
  "sound": true,
  "sound_path": "/storage/emulated/0/AppJadi/SmartNutriChefAI/assets/splash_end.mp3"
}

Berikut  versi splash_screen.py terbaru  dengan dukungan custom path sound:

# splash_screen.py
import tkinter as tk
from tkinter import ttk
import os, json
from PIL import Image, ImageTk
import threading, time
import pygame  # untuk play sound

from menu_utama import tampilkan_halaman_utama

SETTINGS_FILE = "/storage/emulated/0/AppJadi/SmartNutriChefAI/settings.json"

# --- Helper Settings ---
def load_settings():
    if not os.path.exists(SETTINGS_FILE):
        return {
            "sound": True,
            "sound_path": "/storage/emulated/0/AppJadi/SmartNutriChefAI/assets/splash_end.mp3"
        }
    try:
        with open(SETTINGS_FILE, "r") as f:
            data = json.load(f)
            # fallback default jika key belum ada
            if "sound" not in data:
                data["sound"] = True
            if "sound_path" not in data:
                data["sound_path"] = "/storage/emulated/0/AppJadi/SmartNutriChefAI/assets/splash_end.mp3"
            return data
    except:
        return {
            "sound": True,
            "sound_path": "/storage/emulated/0/AppJadi/SmartNutriChefAI/assets/splash_end.mp3"
        }

# --- Splash Screen ---
def show_splash(root):
    splash = tk.Toplevel(root)
    splash.overrideredirect(True)
    splash.geometry("500x350+400+200")
    splash.configure(bg="black")
    splash.attributes("-alpha", 0.0)

    # Logo
    try:
        logo = Image.open("/storage/emulated/0/AppJadi/SmartNutriChefAI/assets/logo.png")
        logo = logo.resize((160, 160))
        logo_img = ImageTk.PhotoImage(logo)
        tk.Label(splash, image=logo_img, bg="black").pack(pady=20)
        splash.logo_img = logo_img
    except:
        tk.Label(splash, text="SmartNutriChefAI", font=("Arial", 22, "bold"), fg="white", bg="black").pack(pady=20)

    # Label Loading
    loading_label = tk.Label(splash, text="Loading", font=("Arial", 16), fg="white", bg="black")
    loading_label.pack(pady=15)

    # Progress bar rainbow
    style = ttk.Style()
    style.theme_use("clam")
    style.configure("Rainbow.Horizontal.TProgressbar", troughcolor="black", thickness=20)

    pb = ttk.Progressbar(splash, mode="indeterminate", length=300, style="Rainbow.Horizontal.TProgressbar")
    pb.pack(pady=15)
    pb.start(15)

    # --- Animasi teks "Loading." "Loading.." "Loading..." ---
    def animate_dots():
        dots = ["Loading", "Loading.", "Loading..", "Loading..."]
        i = 0
        while True:
            time.sleep(0.5)
            i = (i + 1) % len(dots)
            try:
                loading_label.config(text=dots[i], fg=rainbow_colors[i % len(rainbow_colors)])
            except:
                break

    rainbow_colors = ["red", "orange", "yellow", "green", "cyan", "blue", "magenta"]
    threading.Thread(target=animate_dots, daemon=True).start()

    # --- Fade-in efek ---
    def fade_in():
        alpha = splash.attributes("-alpha")
        if alpha < 1.0:
            alpha += 0.05
            splash.attributes("-alpha", alpha)
            splash.after(50, fade_in)
    fade_in()

    # --- Close splash (dengan fade-out) ---
    def close_splash():
        def fade_out():
            alpha = splash.attributes("-alpha")
            if alpha > 0:
                alpha -= 0.05
                splash.attributes("-alpha", alpha)
                splash.after(50, fade_out)
            else:
                splash.destroy()
                tampilkan_halaman_utama(root)

                # πŸ”Š Play sound kalau setting sound: true
                settings = load_settings()
                if settings.get("sound", True):
                    sound_path = settings.get("sound_path")
                    if sound_path and os.path.exists(sound_path):
                        try:
                            pygame.mixer.init()
                            pygame.mixer.music.load(sound_path)
                            pygame.mixer.music.play()
                        except Exception as e:
                            print("Sound error:", e)
                    else:
                        print("⚠ Sound file tidak ditemukan:", sound_path)

        fade_out()

    splash.after(4000, close_splash)  # splash 4 detik

✨ Sekarang:

  1. User bisa atur  sound: true/falsedi  settings.json.
  2. Pengguna juga bisa ganti  sound_path→ misalnya ke  "/storage/emulated/0/Music/my_sound.mp3".
  3. Kalau file tidak ada, splash jalan normal tanpa error fatal.


7. UI di Settings Menu

Berikut juga UI di Settings Menu (pakai tkinter.filedialog.askopenfilename) biar user bisa klik tombol → pilih file mp3/wav langsung tanpa edit JSON manual? πŸŽΆπŸ“‚

Kita buatkan  UI Settings  lebih interaktif → pengguna bisa klik tombol → pilih file suara ( .mp3 /  .wav) langsung dari file explorer tanpa harus edit  settings.json manual.

Kita pakai  tkinter.filedialog.askopenfilename untuk memilih file audio.


πŸ”Ή settings_menu.py (baru)

# settings_menu.py
import tkinter as tk
from tkinter import filedialog, messagebox
import json, os

SETTINGS_FILE = "/storage/emulated/0/AppJadi/SmartNutriChefAI/settings.json"

# --- Helper ---
def load_settings():
    if not os.path.exists(SETTINGS_FILE):
        return {"sound": True, "sound_path": ""}
    try:
        with open(SETTINGS_FILE, "r") as f:
            return json.load(f)
    except:
        return {"sound": True, "sound_path": ""}

def save_settings(data):
    os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
    with open(SETTINGS_FILE, "w") as f:
        json.dump(data, f, indent=4)

# --- UI Settings ---
def show_settings_menu(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="Pengaturan Aplikasi", font=("Arial", 18, "bold")).pack(pady=20)

    settings = load_settings()

    # Checkbox Sound ON/OFF
    sound_var = tk.BooleanVar(value=settings.get("sound", True))
    tk.Checkbutton(root_frame, text="Aktifkan Suara", variable=sound_var, font=("Arial", 12)).pack(pady=10)

    # Path file sound
    tk.Label(root_frame, text="File Suara Splash:", font=("Arial", 12)).pack()
    path_var = tk.StringVar(value=settings.get("sound_path", ""))

    path_entry = tk.Entry(root_frame, textvariable=path_var, width=40)
    path_entry.pack(pady=5)

    def pilih_file():
        file_path = filedialog.askopenfilename(
            title="Pilih File Suara",
            filetypes=[("Audio Files", "*.mp3 *.wav"), ("All Files", "*.*")]
        )
        if file_path:
            path_var.set(file_path)

    tk.Button(root_frame, text="Pilih File...", command=pilih_file, bg="#4CAF50", fg="white").pack(pady=5)

    # Tombol Simpan
    def simpan():
        new_settings = {
            "sound": sound_var.get(),
            "sound_path": path_var.get().strip()
        }
        save_settings(new_settings)
        messagebox.showinfo("Sukses", "Pengaturan berhasil disimpan ✅")

    tk.Button(root_frame, text="Simpan Pengaturan", font=("Arial", 12), bg="blue", fg="white", command=simpan).pack(pady=20)

    # Tombol Kembali
    tk.Button(root_frame, text="⬅ Kembali", font=("Arial", 12), bg="gray", fg="white",
              command=lambda: kembali_ke_menu_utama(root_frame)).pack(pady=20)

def kembali_ke_menu_utama(root_frame):
    from menu_utama import tampilkan_halaman_utama
    tampilkan_halaman_utama(root_frame)

πŸ”Ή Integrasi ke menu_utama.py

Tambahkan import + tombol  Pengaturan  di menu utama:

from settings_menu import show_settings_menu

# ...
menu_items = [
    ("Metode Formula MMAG", lambda: show_menu_metode_formula_mmag(root_frame)),
    ("Pengaturan", lambda: show_settings_menu(root_frame)),  # πŸ”§ Tombol Settings baru
]

✨ Dengan ini:

  1. Pengguna dapat  mengaktifkan/menonaktifkan Suara  secara langsung.
  2. Pengguna bisa memilih  file suara custom  pakai file explorer.
  3. Setting tersimpan ke  settings.json & tetap dipakai di splash screen.


8. preview sound

Berikut menambahkan  preview sound   (tombol ▶ Play untuk coba mendengar file yang dipilih) di Menu Pengaturan ini? 🎡

Kita tambahkan fitur Preview Sound → jadi user bisa langsung cek / play file audio sebelum disimpan.

Kita manfaatkan pygame.mixer (karena tkinter sendiri tidak punya audio player bawaan).


πŸ”Ή settings_menu.py (dengan Pratinjau Suara)

# settings_menu.py
import tkinter as tk
from tkinter import filedialog, messagebox
import json, os

# πŸ”Š Tambahan untuk play sound
import pygame

SETTINGS_FILE = "/storage/emulated/0/AppJadi/SmartNutriChefAI/settings.json"

# --- Helper ---
def load_settings():
    if not os.path.exists(SETTINGS_FILE):
        return {"sound": True, "sound_path": ""}
    try:
        with open(SETTINGS_FILE, "r") as f:
            return json.load(f)
    except:
        return {"sound": True, "sound_path": ""}

def save_settings(data):
    os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
    with open(SETTINGS_FILE, "w") as f:
        json.dump(data, f, indent=4)

# --- UI Settings ---
def show_settings_menu(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="Pengaturan Aplikasi", font=("Arial", 18, "bold")).pack(pady=20)

    settings = load_settings()

    # Checkbox Sound ON/OFF
    sound_var = tk.BooleanVar(value=settings.get("sound", True))
    tk.Checkbutton(root_frame, text="Aktifkan Suara", variable=sound_var, font=("Arial", 12)).pack(pady=10)

    # Path file sound
    tk.Label(root_frame, text="File Suara Splash:", font=("Arial", 12)).pack()
    path_var = tk.StringVar(value=settings.get("sound_path", ""))

    path_entry = tk.Entry(root_frame, textvariable=path_var, width=40)
    path_entry.pack(pady=5)

    def pilih_file():
        file_path = filedialog.askopenfilename(
            title="Pilih File Suara",
            filetypes=[("Audio Files", "*.mp3 *.wav"), ("All Files", "*.*")]
        )
        if file_path:
            path_var.set(file_path)

    tk.Button(root_frame, text="Pilih File...", command=pilih_file, bg="#4CAF50", fg="white").pack(pady=5)

    # --- 🎡 Preview Sound ---
    def preview_sound():
        file_path = path_var.get().strip()
        if not file_path or not os.path.exists(file_path):
            messagebox.showwarning("Peringatan", "File suara tidak valid!")
            return
        try:
            pygame.mixer.init()
            pygame.mixer.music.load(file_path)
            pygame.mixer.music.play()
        except Exception as e:
            messagebox.showerror("Error", f"Gagal memutar suara:\n{e}")

    tk.Button(root_frame, text="▶ Preview Suara", command=preview_sound, bg="orange", fg="black").pack(pady=5)

    # Tombol Simpan
    def simpan():
        new_settings = {
            "sound": sound_var.get(),
            "sound_path": path_var.get().strip()
        }
        save_settings(new_settings)
        messagebox.showinfo("Sukses", "Pengaturan berhasil disimpan ✅")

    tk.Button(root_frame, text="Simpan Pengaturan", font=("Arial", 12), bg="blue", fg="white", command=simpan).pack(pady=20)

    # Tombol Kembali
    tk.Button(root_frame, text="⬅ Kembali", font=("Arial", 12), bg="gray", fg="white",
              command=lambda: kembali_ke_menu_utama(root_frame)).pack(pady=20)

def kembali_ke_menu_utama(root_frame):
    from menu_utama import tampilkan_halaman_utama
    tampilkan_halaman_utama(root_frame)

πŸ”Ή Fitur Baru

  1. Pratinjau Suara  ( ▶ Preview Suara) → memutar file yang dipilih.
  2. Kalau file tidak valid / tidak ditemukan → muncul warning.
  3. Tetap bisa mengaktifkan  Sound ON/OFF  dan pilih file seperti sebelumnya.


9. tombol ⏹ Stop Sound 

Berikut menambahkan juga  tombol ⏹ Stop Sound  biar pengguna bisa menghentikan preview sebelum selesai?

Kita tambahkan tombol  ⏹ Stop Sound  supaya pengguna bisa menghentikan preview sebelum selesai.
Kita manfaatkan  pygame.mixer.music.stop() untuk menghentikan audio.


πŸ”Ή settings_menu.py (versi dengan tombol ▶ Play & ⏹ Stop)

# settings_menu.py
import tkinter as tk
from tkinter import filedialog, messagebox
import json, os
import pygame  # πŸ”Š Untuk play/stop sound

SETTINGS_FILE = "/storage/emulated/0/AppJadi/SmartNutriChefAI/settings.json"

# --- Helper ---
def load_settings():
    if not os.path.exists(SETTINGS_FILE):
        return {"sound": True, "sound_path": ""}
    try:
        with open(SETTINGS_FILE, "r") as f:
            return json.load(f)
    except:
        return {"sound": True, "sound_path": ""}

def save_settings(data):
    os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
    with open(SETTINGS_FILE, "w") as f:
        json.dump(data, f, indent=4)

# --- UI Settings ---
def show_settings_menu(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="Pengaturan Aplikasi", font=("Arial", 18, "bold")).pack(pady=20)

    settings = load_settings()

    # Checkbox Sound ON/OFF
    sound_var = tk.BooleanVar(value=settings.get("sound", True))
    tk.Checkbutton(root_frame, text="Aktifkan Suara", variable=sound_var, font=("Arial", 12)).pack(pady=10)

    # Path file sound
    tk.Label(root_frame, text="File Suara Splash:", font=("Arial", 12)).pack()
    path_var = tk.StringVar(value=settings.get("sound_path", ""))

    path_entry = tk.Entry(root_frame, textvariable=path_var, width=40)
    path_entry.pack(pady=5)

    def pilih_file():
        file_path = filedialog.askopenfilename(
            title="Pilih File Suara",
            filetypes=[("Audio Files", "*.mp3 *.wav"), ("All Files", "*.*")]
        )
        if file_path:
            path_var.set(file_path)

    tk.Button(root_frame, text="Pilih File...", command=pilih_file, bg="#4CAF50", fg="white").pack(pady=5)

    # --- 🎡 Preview Sound ---
    def preview_sound():
        file_path = path_var.get().strip()
        if not file_path or not os.path.exists(file_path):
            messagebox.showwarning("Peringatan", "File suara tidak valid!")
            return
        try:
            pygame.mixer.init()
            pygame.mixer.music.load(file_path)
            pygame.mixer.music.play()
        except Exception as e:
            messagebox.showerror("Error", f"Gagal memutar suara:\n{e}")

    def stop_sound():
        try:
            if pygame.mixer.get_init():
                pygame.mixer.music.stop()
        except:
            pass

    # Frame tombol play/stop biar sejajar
    btn_preview_frame = tk.Frame(root_frame)
    btn_preview_frame.pack(pady=5)

    tk.Button(btn_preview_frame, text="▶ Preview Suara", command=preview_sound,
              bg="orange", fg="black", width=18).pack(side="left", padx=5)

    tk.Button(btn_preview_frame, text="⏹ Stop Suara", command=stop_sound,
              bg="red", fg="white", width=18).pack(side="left", padx=5)

    # Tombol Simpan
    def simpan():
        new_settings = {
            "sound": sound_var.get(),
            "sound_path": path_var.get().strip()
        }
        save_settings(new_settings)
        messagebox.showinfo("Sukses", "Pengaturan berhasil disimpan ✅")

    tk.Button(root_frame, text="Simpan Pengaturan", font=("Arial", 12),
              bg="blue", fg="white", command=simpan).pack(pady=20)

    # Tombol Kembali
    tk.Button(root_frame, text="⬅ Kembali", font=("Arial", 12),
              bg="gray", fg="white",
              command=lambda: kembali_ke_menu_utama(root_frame)).pack(pady=20)

def kembali_ke_menu_utama(root_frame):
    from menu_utama import tampilkan_halaman_utama
    tampilkan_halaman_utama(root_frame)

πŸ”Ή Perubahan Baru

  • Sekarang ada  dua tombol preview :
    • ▶ Preview Suara → play file yang dipilih.
    • ⏹ Stop Suara → hentikan suara langsung tanpa menunggu selesai.
  • Kedua tombol ditaruh  sejajar  dalam satu  Frame.


10. fitur auto-stop 

Berikut menambahkan juga  fitur auto-stop  (misalnya jika pengguna klik  Simpan Pengaturan, suara preview otomatis berhenti)?

Mantap πŸ‘ biar lebih rapi pengalaman user.
Jadi saat pengguna menekan  Simpan Pengaturan, kami pastikan  pratinjau suara dihentikan otomatis  → supaya tidak ada kasus suara tetap jalan meskipun pengaturan sudah disimpan.


πŸ”Ή Pembaruan Versi  settings_menu.py (berhenti otomatis di Simpan)

# settings_menu.py
import tkinter as tk
from tkinter import filedialog, messagebox
import json, os
import pygame  # πŸ”Š Untuk play/stop sound

SETTINGS_FILE = "/storage/emulated/0/AppJadi/SmartNutriChefAI/settings.json"

# --- Helper ---
def load_settings():
    if not os.path.exists(SETTINGS_FILE):
        return {"sound": True, "sound_path": ""}
    try:
        with open(SETTINGS_FILE, "r") as f:
            return json.load(f)
    except:
        return {"sound": True, "sound_path": ""}

def save_settings(data):
    os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
    with open(SETTINGS_FILE, "w") as f:
        json.dump(data, f, indent=4)

def stop_sound():
    """Hentikan suara (jika sedang play)."""
    try:
        if pygame.mixer.get_init():
            pygame.mixer.music.stop()
    except:
        pass

# --- UI Settings ---
def show_settings_menu(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="Pengaturan Aplikasi", font=("Arial", 18, "bold")).pack(pady=20)

    settings = load_settings()

    # Checkbox Sound ON/OFF
    sound_var = tk.BooleanVar(value=settings.get("sound", True))
    tk.Checkbutton(root_frame, text="Aktifkan Suara", variable=sound_var, font=("Arial", 12)).pack(pady=10)

    # Path file sound
    tk.Label(root_frame, text="File Suara Splash:", font=("Arial", 12)).pack()
    path_var = tk.StringVar(value=settings.get("sound_path", ""))

    path_entry = tk.Entry(root_frame, textvariable=path_var, width=40)
    path_entry.pack(pady=5)

    def pilih_file():
        file_path = filedialog.askopenfilename(
            title="Pilih File Suara",
            filetypes=[("Audio Files", "*.mp3 *.wav"), ("All Files", "*.*")]
        )
        if file_path:
            path_var.set(file_path)

    tk.Button(root_frame, text="Pilih File...", command=pilih_file, bg="#4CAF50", fg="white").pack(pady=5)

    # --- 🎡 Preview Sound ---
    def preview_sound():
        file_path = path_var.get().strip()
        if not file_path or not os.path.exists(file_path):
            messagebox.showwarning("Peringatan", "File suara tidak valid!")
            return
        try:
            pygame.mixer.init()
            pygame.mixer.music.load(file_path)
            pygame.mixer.music.play()
        except Exception as e:
            messagebox.showerror("Error", f"Gagal memutar suara:\n{e}")

    # Frame tombol play/stop biar sejajar
    btn_preview_frame = tk.Frame(root_frame)
    btn_preview_frame.pack(pady=5)

    tk.Button(btn_preview_frame, text="▶ Preview Suara", command=preview_sound,
              bg="orange", fg="black", width=18).pack(side="left", padx=5)

    tk.Button(btn_preview_frame, text="⏹ Stop Suara", command=stop_sound,
              bg="red", fg="white", width=18).pack(side="left", padx=5)

    # Tombol Simpan
    def simpan():
        # 🚫 Stop suara dulu sebelum simpan
        stop_sound()

        new_settings = {
            "sound": sound_var.get(),
            "sound_path": path_var.get().strip()
        }
        save_settings(new_settings)
        messagebox.showinfo("Sukses", "Pengaturan berhasil disimpan ✅")

    tk.Button(root_frame, text="Simpan Pengaturan", font=("Arial", 12),
              bg="blue", fg="white", command=simpan).pack(pady=20)

    # Tombol Kembali
    tk.Button(root_frame, text="⬅ Kembali", font=("Arial", 12),
              bg="gray", fg="white",
              command=lambda: kembali_ke_menu_utama(root_frame)).pack(pady=20)

def kembali_ke_menu_utama(root_frame):
    from menu_utama import tampilkan_halaman_utama
    tampilkan_halaman_utama(root_frame)

πŸ”Ή Perubahan Baru:

  • Tambahkan  fungsi global stop_sound()  (bisa dipanggil dari mana saja).
  • Saat pengguna mengklik  Simpan , sistem otomatis  menghentikan suara pratinjau  dulu → pengaturan simpan baru.


11. tekan tombol Kembali  

Berikut tekan tombol Kembali  → otomatis menghentikan suara pratinjau? (supaya tidak ada kasus user keluar tapi suaranya masih menyala 🎡).

Betul πŸ‘ itu penting banget, karena kalau pengguna tekan  Kembali  tapi preview sound masih jalan → kesannya aplikasi jadi error atau “nggak nurut”.
Kita bisa menggunakan  fungsi stop_sound()  yang tadi sudah kita buat, lalu memanggil sebelum berpindah ke Menu Utama.


πŸ”Ή Pembaruan Versi  settings_menu.py (Stop Sound juga saat Kembali)

# settings_menu.py
import tkinter as tk
from tkinter import filedialog, messagebox
import json, os
import pygame  # πŸ”Š Untuk play/stop sound

SETTINGS_FILE = "/storage/emulated/0/AppJadi/SmartNutriChefAI/settings.json"

# --- Helper ---
def load_settings():
    if not os.path.exists(SETTINGS_FILE):
        return {"sound": True, "sound_path": ""}
    try:
        with open(SETTINGS_FILE, "r") as f:
            return json.load(f)
    except:
        return {"sound": True, "sound_path": ""}

def save_settings(data):
    os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
    with open(SETTINGS_FILE, "w") as f:
        json.dump(data, f, indent=4)

def stop_sound():
    """Hentikan suara (jika sedang play)."""
    try:
        if pygame.mixer.get_init():
            pygame.mixer.music.stop()
    except:
        pass

# --- UI Settings ---
def show_settings_menu(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="Pengaturan Aplikasi", font=("Arial", 18, "bold")).pack(pady=20)

    settings = load_settings()

    # Checkbox Sound ON/OFF
    sound_var = tk.BooleanVar(value=settings.get("sound", True))
    tk.Checkbutton(root_frame, text="Aktifkan Suara", variable=sound_var, font=("Arial", 12)).pack(pady=10)

    # Path file sound
    tk.Label(root_frame, text="File Suara Splash:", font=("Arial", 12)).pack()
    path_var = tk.StringVar(value=settings.get("sound_path", ""))

    path_entry = tk.Entry(root_frame, textvariable=path_var, width=40)
    path_entry.pack(pady=5)

    def pilih_file():
        file_path = filedialog.askopenfilename(
            title="Pilih File Suara",
            filetypes=[("Audio Files", "*.mp3 *.wav"), ("All Files", "*.*")]
        )
        if file_path:
            path_var.set(file_path)

    tk.Button(root_frame, text="Pilih File...", command=pilih_file, bg="#4CAF50", fg="white").pack(pady=5)

    # --- 🎡 Preview Sound ---
    def preview_sound():
        file_path = path_var.get().strip()
        if not file_path or not os.path.exists(file_path):
            messagebox.showwarning("Peringatan", "File suara tidak valid!")
            return
        try:
            pygame.mixer.init()
            pygame.mixer.music.load(file_path)
            pygame.mixer.music.play()
        except Exception as e:
            messagebox.showerror("Error", f"Gagal memutar suara:\n{e}")

    # Frame tombol play/stop biar sejajar
    btn_preview_frame = tk.Frame(root_frame)
    btn_preview_frame.pack(pady=5)

    tk.Button(btn_preview_frame, text="▶ Preview Suara", command=preview_sound,
              bg="orange", fg="black", width=18).pack(side="left", padx=5)

    tk.Button(btn_preview_frame, text="⏹ Stop Suara", command=stop_sound,
              bg="red", fg="white", width=18).pack(side="left", padx=5)

    # Tombol Simpan
    def simpan():
        # 🚫 Stop suara dulu sebelum simpan
        stop_sound()

        new_settings = {
            "sound": sound_var.get(),
            "sound_path": path_var.get().strip()
        }
        save_settings(new_settings)
        messagebox.showinfo("Sukses", "Pengaturan berhasil disimpan ✅")

    tk.Button(root_frame, text="Simpan Pengaturan", font=("Arial", 12),
              bg="blue", fg="white", command=simpan).pack(pady=20)

    # Tombol Kembali
    def kembali():
        # 🚫 Stop juga kalau user keluar
        stop_sound()
        from menu_utama import tampilkan_halaman_utama
        tampilkan_halaman_utama(root_frame)

    tk.Button(root_frame, text="⬅ Kembali", font=("Arial", 12),
              bg="gray", fg="white", command=kembali).pack(pady=20)

πŸ”Ή Perbedaan baru:

  • stop_sound() dipanggil di fungsi kembali()  sebelum berpindah ke menu utama.
  • Jadi pengguna  tidak mungkin membawa suara preview yang masih menyala  ke menu utama. πŸ”‡


12. opsi auto-stop juga kalau pengguna mengklik tombol Play lagi 

mau kamu menambahkan  opsi auto-stop juga kalau pengguna mengklik tombol Play lagi (sebelum lagu sebelumnya selesai)?  Jadi musik lama berhenti → langsung ganti play baru. 🎢

Mantap πŸ”₯ itu ide bagus supaya tidak ada kasus  suara tumpang-tindih  kalau pengguna tekan tombol ▶ Mainkan berkali-kali.
Caranya mudah: sebelumnya  pygame.mixer.music.load(...), kita telepon dulu  stop_sound().


πŸ”Ή Pembaruan Versi  settings_menu.py dengan Auto-Stop saat Putar Lagi

# settings_menu.py
import tkinter as tk
from tkinter import filedialog, messagebox
import json, os
import pygame  # πŸ”Š Untuk play/stop sound

SETTINGS_FILE = "/storage/emulated/0/AppJadi/SmartNutriChefAI/settings.json"

# --- Helper ---
def load_settings():
    if not os.path.exists(SETTINGS_FILE):
        return {"sound": True, "sound_path": ""}
    try:
        with open(SETTINGS_FILE, "r") as f:
            return json.load(f)
    except:
        return {"sound": True, "sound_path": ""}

def save_settings(data):
    os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
    with open(SETTINGS_FILE, "w") as f:
        json.dump(data, f, indent=4)

def stop_sound():
    """Hentikan suara (jika sedang play)."""
    try:
        if pygame.mixer.get_init():
            pygame.mixer.music.stop()
    except:
        pass

# --- UI Settings ---
def show_settings_menu(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="Pengaturan Aplikasi", font=("Arial", 18, "bold")).pack(pady=20)

    settings = load_settings()

    # Checkbox Sound ON/OFF
    sound_var = tk.BooleanVar(value=settings.get("sound", True))
    tk.Checkbutton(root_frame, text="Aktifkan Suara", variable=sound_var, font=("Arial", 12)).pack(pady=10)

    # Path file sound
    tk.Label(root_frame, text="File Suara Splash:", font=("Arial", 12)).pack()
    path_var = tk.StringVar(value=settings.get("sound_path", ""))

    path_entry = tk.Entry(root_frame, textvariable=path_var, width=40)
    path_entry.pack(pady=5)

    def pilih_file():
        file_path = filedialog.askopenfilename(
            title="Pilih File Suara",
            filetypes=[("Audio Files", "*.mp3 *.wav"), ("All Files", "*.*")]
        )
        if file_path:
            path_var.set(file_path)

    tk.Button(root_frame, text="Pilih File...", command=pilih_file, bg="#4CAF50", fg="white").pack(pady=5)

    # --- 🎡 Preview Sound ---
    def preview_sound():
        file_path = path_var.get().strip()
        if not file_path or not os.path.exists(file_path):
            messagebox.showwarning("Peringatan", "File suara tidak valid!")
            return
        try:
            # πŸ”Ή Auto-stop sebelum play ulang
            stop_sound()
            if not pygame.mixer.get_init():
                pygame.mixer.init()
            pygame.mixer.music.load(file_path)
            pygame.mixer.music.play()
        except Exception as e:
            messagebox.showerror("Error", f"Gagal memutar suara:\n{e}")

    # Frame tombol play/stop biar sejajar
    btn_preview_frame = tk.Frame(root_frame)
    btn_preview_frame.pack(pady=5)

    tk.Button(btn_preview_frame, text="▶ Preview Suara", command=preview_sound,
              bg="orange", fg="black", width=18).pack(side="left", padx=5)

    tk.Button(btn_preview_frame, text="⏹ Stop Suara", command=stop_sound,
              bg="red", fg="white", width=18).pack(side="left", padx=5)

    # Tombol Simpan
    def simpan():
        # 🚫 Stop suara dulu sebelum simpan
        stop_sound()

        new_settings = {
            "sound": sound_var.get(),
            "sound_path": path_var.get().strip()
        }
        save_settings(new_settings)
        messagebox.showinfo("Sukses", "Pengaturan berhasil disimpan ✅")

    tk.Button(root_frame, text="Simpan Pengaturan", font=("Arial", 12),
              bg="blue", fg="white", command=simpan).pack(pady=20)

    # Tombol Kembali
    def kembali():
        # 🚫 Stop juga kalau user keluar
        stop_sound()
        from menu_utama import tampilkan_halaman_utama
        tampilkan_halaman_utama(root_frame)

    tk.Button(root_frame, text="⬅ Kembali", font=("Arial", 12),
              bg="gray", fg="white", command=kembali).pack(pady=20)

πŸ”Ή Perubahan Utama:

  1. stop_sound() dipanggil di awal preview_sound()  → kalau ada suara lama, langsung dihentikan.
  2. Jadi pengguna bisa klik ▶ Mainkan berkali-kali, tapi  suara lama berhenti dulu  sebelum memulai yang baru → tidak tumpang tindih. 🎢✅


13. Opsi loop (repeat)

Berikut juga opsi  loop (repeat)  untuk preview suara? πŸ” Jadi kalau pengguna mencentang “Loop Preview”, suara akan terus berulang sampai ditekan ⏹ Stop.

Kami menambahkan opsi  Loop Pratinjau  (pakai  Checkbutton) → kalau aktif, maka  pygame.mixer.music.play(-1) (loop tak terbatas).
Kalau tidak dicentang, tetap pakai  .play() sekali saja.


πŸ”Ή Pembaruan Versi  settings_menu.py dengan  Pratinjau Loop

# settings_menu.py
import tkinter as tk
from tkinter import filedialog, messagebox
import json, os
import pygame  # πŸ”Š Untuk play/stop sound

SETTINGS_FILE = "/storage/emulated/0/AppJadi/SmartNutriChefAI/settings.json"

# --- Helper ---
def load_settings():
    if not os.path.exists(SETTINGS_FILE):
        return {"sound": True, "sound_path": ""}
    try:
        with open(SETTINGS_FILE, "r") as f:
            return json.load(f)
    except:
        return {"sound": True, "sound_path": ""}

def save_settings(data):
    os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
    with open(SETTINGS_FILE, "w") as f:
        json.dump(data, f, indent=4)

def stop_sound():
    """Hentikan suara (jika sedang play)."""
    try:
        if pygame.mixer.get_init():
            pygame.mixer.music.stop()
    except:
        pass

# --- UI Settings ---
def show_settings_menu(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="Pengaturan Aplikasi", font=("Arial", 18, "bold")).pack(pady=20)

    settings = load_settings()

    # Checkbox Sound ON/OFF
    sound_var = tk.BooleanVar(value=settings.get("sound", True))
    tk.Checkbutton(root_frame, text="Aktifkan Suara", variable=sound_var, font=("Arial", 12)).pack(pady=10)

    # Path file sound
    tk.Label(root_frame, text="File Suara Splash:", font=("Arial", 12)).pack()
    path_var = tk.StringVar(value=settings.get("sound_path", ""))

    path_entry = tk.Entry(root_frame, textvariable=path_var, width=40)
    path_entry.pack(pady=5)

    def pilih_file():
        file_path = filedialog.askopenfilename(
            title="Pilih File Suara",
            filetypes=[("Audio Files", "*.mp3 *.wav"), ("All Files", "*.*")]
        )
        if file_path:
            path_var.set(file_path)

    tk.Button(root_frame, text="Pilih File...", command=pilih_file, bg="#4CAF50", fg="white").pack(pady=5)

    # --- 🎡 Preview Sound ---
    loop_var = tk.BooleanVar(value=False)  # πŸ” Default: tidak loop
    tk.Checkbutton(root_frame, text="Loop Preview (Ulangi terus)", variable=loop_var, font=("Arial", 11)).pack(pady=5)

    def preview_sound():
        file_path = path_var.get().strip()
        if not file_path or not os.path.exists(file_path):
            messagebox.showwarning("Peringatan", "File suara tidak valid!")
            return
        try:
            # πŸ”Ή Auto-stop sebelum play ulang
            stop_sound()
            if not pygame.mixer.get_init():
                pygame.mixer.init()
            pygame.mixer.music.load(file_path)
            if loop_var.get():
                pygame.mixer.music.play(-1)  # πŸ” Loop infinite
            else:
                pygame.mixer.music.play()    # ▶ Sekali play
        except Exception as e:
            messagebox.showerror("Error", f"Gagal memutar suara:\n{e}")

    # Frame tombol play/stop biar sejajar
    btn_preview_frame = tk.Frame(root_frame)
    btn_preview_frame.pack(pady=5)

    tk.Button(btn_preview_frame, text="▶ Preview Suara", command=preview_sound,
              bg="orange", fg="black", width=18).pack(side="left", padx=5)

    tk.Button(btn_preview_frame, text="⏹ Stop Suara", command=stop_sound,
              bg="red", fg="white", width=18).pack(side="left", padx=5)

    # Tombol Simpan
    def simpan():
        # 🚫 Stop suara dulu sebelum simpan
        stop_sound()

        new_settings = {
            "sound": sound_var.get(),
            "sound_path": path_var.get().strip()
        }
        save_settings(new_settings)
        messagebox.showinfo("Sukses", "Pengaturan berhasil disimpan ✅")

    tk.Button(root_frame, text="Simpan Pengaturan", font=("Arial", 12),
              bg="blue", fg="white", command=simpan).pack(pady=20)

    # Tombol Kembali
    def kembali():
        # 🚫 Stop juga kalau user keluar
        stop_sound()
        from menu_utama import tampilkan_halaman_utama
        tampilkan_halaman_utama(root_frame)

    tk.Button(root_frame, text="⬅ Kembali", font=("Arial", 12),
              bg="gray", fg="white", command=kembali).pack(pady=20)

πŸ”Ή Perubahan Utama:

  1. Tambah  loop_var = tk.BooleanVar() + Tombol centang “Pratinjau Loop”.
  2. Kalau dicentang →  .play(-1) (loop tanpa henti).
  3. Kalau tidak dicentang →  .play() (sekali saja).
  4. Stop otomatis tetap aktif jika pengguna tekan  Stop / Simpan / Kembali .


14. psi atur jumlah loop  

Berikut  juga opsi  atur jumlah loop  (misalnya 3x, 5x) selain unlimited loop? πŸ”πŸ‘‰πŸ”’

kita bikin lebih fleksibel: user bisa pilih  sekali playloop tak terbatas (-1) , atau  jumlah tertentu (misalnya 3x, 5x) .
Saya tambahkan  Spinbox untuk pilih jumlah loop → default  1 (sekali), jika pengguna pilih angka >1 → suara diputar sebanyak angka itu.


πŸ”Ή Pembaruan Versi  settings_menu.py dengan  Loop Custom

# settings_menu.py
import tkinter as tk
from tkinter import filedialog, messagebox
import json, os
import pygame  # πŸ”Š Untuk play/stop sound

SETTINGS_FILE = "/storage/emulated/0/AppJadi/SmartNutriChefAI/settings.json"

# --- Helper ---
def load_settings():
    if not os.path.exists(SETTINGS_FILE):
        return {"sound": True, "sound_path": ""}
    try:
        with open(SETTINGS_FILE, "r") as f:
            return json.load(f)
    except:
        return {"sound": True, "sound_path": ""}

def save_settings(data):
    os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
    with open(SETTINGS_FILE, "w") as f:
        json.dump(data, f, indent=4)

def stop_sound():
    """Hentikan suara (jika sedang play)."""
    try:
        if pygame.mixer.get_init():
            pygame.mixer.music.stop()
    except:
        pass

# --- UI Settings ---
def show_settings_menu(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="Pengaturan Aplikasi", font=("Arial", 18, "bold")).pack(pady=20)

    settings = load_settings()

    # Checkbox Sound ON/OFF
    sound_var = tk.BooleanVar(value=settings.get("sound", True))
    tk.Checkbutton(root_frame, text="Aktifkan Suara", variable=sound_var, font=("Arial", 12)).pack(pady=10)

    # Path file sound
    tk.Label(root_frame, text="File Suara Splash:", font=("Arial", 12)).pack()
    path_var = tk.StringVar(value=settings.get("sound_path", ""))

    path_entry = tk.Entry(root_frame, textvariable=path_var, width=40)
    path_entry.pack(pady=5)

    def pilih_file():
        file_path = filedialog.askopenfilename(
            title="Pilih File Suara",
            filetypes=[("Audio Files", "*.mp3 *.wav"), ("All Files", "*.*")]
        )
        if file_path:
            path_var.set(file_path)

    tk.Button(root_frame, text="Pilih File...", command=pilih_file, bg="#4CAF50", fg="white").pack(pady=5)

    # --- 🎡 Preview Sound ---
    loop_mode = tk.StringVar(value="once")  # default sekali
    tk.Label(root_frame, text="Mode Loop Preview:", font=("Arial", 12)).pack(pady=5)

    frame_loop = tk.Frame(root_frame)
    frame_loop.pack(pady=5)

    tk.Radiobutton(frame_loop, text="Sekali", variable=loop_mode, value="once").pack(side="left", padx=5)
    tk.Radiobutton(frame_loop, text="Tak Terbatas", variable=loop_mode, value="infinite").pack(side="left", padx=5)
    tk.Radiobutton(frame_loop, text="Custom (x kali)", variable=loop_mode, value="custom").pack(side="left", padx=5)

    # Spinbox untuk jumlah loop (default 3x)
    loop_count_var = tk.IntVar(value=3)
    spin = tk.Spinbox(root_frame, from_=2, to=50, textvariable=loop_count_var, width=5)
    spin.pack(pady=5)

    def preview_sound():
        file_path = path_var.get().strip()
        if not file_path or not os.path.exists(file_path):
            messagebox.showwarning("Peringatan", "File suara tidak valid!")
            return
        try:
            # πŸ”Ή Auto-stop sebelum play ulang
            stop_sound()
            if not pygame.mixer.get_init():
                pygame.mixer.init()
            pygame.mixer.music.load(file_path)

            mode = loop_mode.get()
            if mode == "infinite":
                pygame.mixer.music.play(-1)  # πŸ” Loop infinite
            elif mode == "custom":
                count = max(1, loop_count_var.get())
                pygame.mixer.music.play(count - 1)  # pygame: 0 = sekali, n = ulang n kali
            else:
                pygame.mixer.music.play()  # ▶ sekali
        except Exception as e:
            messagebox.showerror("Error", f"Gagal memutar suara:\n{e}")

    # Frame tombol play/stop biar sejajar
    btn_preview_frame = tk.Frame(root_frame)
    btn_preview_frame.pack(pady=5)

    tk.Button(btn_preview_frame, text="▶ Preview Suara", command=preview_sound,
              bg="orange", fg="black", width=18).pack(side="left", padx=5)

    tk.Button(btn_preview_frame, text="⏹ Stop Suara", command=stop_sound,
              bg="red", fg="white", width=18).pack(side="left", padx=5)

    # Tombol Simpan
    def simpan():
        # 🚫 Stop suara dulu sebelum simpan
        stop_sound()

        new_settings = {
            "sound": sound_var.get(),
            "sound_path": path_var.get().strip()
        }
        save_settings(new_settings)
        messagebox.showinfo("Sukses", "Pengaturan berhasil disimpan ✅")

    tk.Button(root_frame, text="Simpan Pengaturan", font=("Arial", 12),
              bg="blue", fg="white", command=simpan).pack(pady=20)

    # Tombol Kembali
    def kembali():
        # 🚫 Stop juga kalau user keluar
        stop_sound()
        from menu_utama import tampilkan_halaman_utama
        tampilkan_halaman_utama(root_frame)

    tk.Button(root_frame, text="⬅ Kembali", font=("Arial", 12),
              bg="gray", fg="white", command=kembali).pack(pady=20)

πŸ”Ή Cara Kerja:

  • Tombol Radio
    • Sekali → .play()
    • Tak terbatas → .play(-1)
    • Kustom (X kali) → gunakan  Spinbox (misal 3 →  .play(2))
  • Spinbox aktif
  • Semua auto-stop masih berlaku jika  Stop / Simpan / Kembali  ditekan.

Mau saya tambahkan  nonaktifkan Spinbox otomatis  kalau bukan mode "Custom"? (biar lebih ramah UX) πŸ”§



Bersambung 

Comments

Popular posts from this blog

SPLASH_SCREEN AWAL MEMULAI MEMBUAT APPLIKASI BERBASIS PYTHON