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) karenaplaysoundsuka 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:
- Begitu splash selesai → efek suara otomatis dimainkan πΆ
- Kamu bisa pakai .mp3 (misalnya suara “ding” / “welcome”) atau .wav (lebih ringan).
- 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) tersimpan
settings.jsondi 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:
load_settings()dipakai untuk cek preferensisound.- Kalau begitu
sound = false, suara splash tidak diputar . - File suara (
splash_end.mp3) bisa kamu taruh di folderassets/. - 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:
- User bisa atur
sound: true/falsedisettings.json. - Pengguna juga bisa ganti
sound_path→ misalnya ke"/storage/emulated/0/Music/my_sound.mp3". - 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:
- Pengguna dapat mengaktifkan/menonaktifkan Suara secara langsung.
- Pengguna bisa memilih file suara custom pakai file explorer.
- 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
- Pratinjau Suara (
▶ Preview Suara) → memutar file yang dipilih. - Kalau file tidak valid / tidak ditemukan → muncul warning.
- 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 fungsikembali()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:
stop_sound()dipanggil di awalpreview_sound()→ kalau ada suara lama, langsung dihentikan.- 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:
- Tambah
loop_var = tk.BooleanVar()+ Tombol centang “Pratinjau Loop”. - Kalau dicentang →
.play(-1)(loop tanpa henti). - Kalau tidak dicentang →
.play()(sekali saja). - 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 play , loop 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))
- Sekali →
- 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
Post a Comment