Saba-python/App_GUI.py
2026-03-23 19:29:24 +03:30

250 lines
10 KiB
Python

import customtkinter as ctk
# GUI Configuration
ctk.set_appearance_mode("Dark")
ctk.set_default_color_theme("blue")
class AppGUI(ctk.CTk):
def __init__(self, controller):
super().__init__()
self.controller = controller
self.title("Secure SMS - Raspberry Pi")
self.geometry("800x480") # Typical Pi touchscreen resolution
# Current state
self.current_contact = None
self.setup_ui()
self.load_contacts()
def setup_ui(self):
# Grid Layout (1x2) - Sidebar | Main Content
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=1)
# --- Sidebar ---
self.sidebar_frame = ctk.CTkFrame(self, width=200, corner_radius=0)
self.sidebar_frame.grid(row=0, column=0, sticky="nsew")
self.sidebar_frame.grid_rowconfigure(4, weight=1)
self.logo_label = ctk.CTkLabel(self.sidebar_frame, text="Secure SMS", font=ctk.CTkFont(size=20, weight="bold"))
self.logo_label.grid(row=0, column=0, padx=20, pady=(20, 10))
self.contacts_scrollable_frame = ctk.CTkScrollableFrame(self.sidebar_frame, label_text="Contacts")
self.contacts_scrollable_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew", rowspan=4)
# Settings / Add Contact Button
self.settings_btn = ctk.CTkButton(self.sidebar_frame, text="Settings / Add Contact", command=self.open_settings)
self.settings_btn.grid(row=5, column=0, padx=10, pady=20)
# --- Main Chat Area ---
self.main_frame = ctk.CTkFrame(self, corner_radius=0)
self.main_frame.grid(row=0, column=1, sticky="nsew")
self.main_frame.grid_rowconfigure(1, weight=1)
self.main_frame.grid_columnconfigure(0, weight=1)
# Chat Header
self.chat_header_var = ctk.StringVar(value="Select a contact")
self.chat_header = ctk.CTkLabel(self.main_frame, textvariable=self.chat_header_var, font=ctk.CTkFont(size=18, weight="bold"))
self.chat_header.grid(row=0, column=0, padx=20, pady=10, sticky="w")
# Secured Status Label Indicator
self.header_secure_status = ctk.CTkLabel(self.main_frame, text="Current Mode: Normal", text_color="yellow")
self.header_secure_status.grid(row=0, column=0, padx=20, pady=10, sticky="e")
# Messages Display
self.msg_display = ctk.CTkTextbox(self.main_frame, state="disabled", wrap="word")
self.msg_display.grid(row=1, column=0, padx=20, pady=(0, 10), sticky="nsew")
self.msg_display.tag_config('sent_normal', foreground='lightgray', justify='right')
self.msg_display.tag_config('sent_secure', foreground='lightgreen', justify='right')
self.msg_display.tag_config('recv_normal', foreground='white', justify='left')
self.msg_display.tag_config('recv_secure', foreground='mediumspringgreen', justify='left')
self.msg_display.tag_config('system', foreground='red', justify='center')
# Bottom Input Area
self.input_frame = ctk.CTkFrame(self.main_frame, height=50)
self.input_frame.grid(row=2, column=0, padx=20, pady=10, sticky="nsew")
self.input_frame.grid_columnconfigure(0, weight=1)
self.msg_entry = ctk.CTkEntry(self.input_frame, placeholder_text="Type a message...")
self.msg_entry.grid(row=0, column=0, padx=(10, 5), pady=10, sticky="nsew")
self.msg_entry.bind("<Return>", lambda e: self.send_message())
# Toggle Secure SMS
self.secure_mode_var = ctk.StringVar(value="off")
self.secure_switch = ctk.CTkSwitch(
self.input_frame,
text="Secure",
command=self.toggle_mode,
variable=self.secure_mode_var,
onvalue="on",
offvalue="off"
)
self.secure_switch.grid(row=0, column=1, padx=5, pady=10)
self.send_btn = ctk.CTkButton(self.input_frame, text="Send", width=80, command=self.send_message)
self.send_btn.grid(row=0, column=2, padx=(5, 10), pady=10)
def toggle_mode(self):
mode = "Secure" if self.secure_mode_var.get() == "on" else "Normal"
color = "mediumspringgreen" if mode == "Secure" else "yellow"
self.header_secure_status.configure(text=f"Current Mode: {mode}", text_color=color)
def load_contacts(self):
# Clear sidebar list
for widget in self.contacts_scrollable_frame.winfo_children():
widget.destroy()
contacts = self.controller.get_contacts()
for idx, contact in enumerate(contacts):
# contact = (name, phone, pubkey)
name, phone, _ = contact
btn = ctk.CTkButton(
self.contacts_scrollable_frame,
text=f"{name}\n{phone}",
command=lambda p=phone: self.select_contact(p),
fg_color="transparent",
border_width=2,
text_color=("gray10", "#DCE4EE")
)
btn.grid(row=idx, column=0, padx=5, pady=5, sticky="ew")
def select_contact(self, phone):
self.current_contact = self.controller.get_contact_info(phone)
name = self.current_contact[0]
self.chat_header_var.set(f"Chat with {name} ({phone})")
# Determine if we can do secure
has_key = self.current_contact[2] is not None
if not has_key:
self.secure_switch.deselect()
self.secure_switch.configure(state="disabled")
self.toggle_mode()
self.append_system_msg("This contact has no public key. Secure mode disabled.")
else:
self.secure_switch.configure(state="normal")
self.refresh_messages()
def refresh_messages(self):
self.msg_display.configure(state="normal")
self.msg_display.delete("1.0", "end")
if self.current_contact:
messages = self.controller.get_messages(self.current_contact[1])
for msg in messages:
# msg = (id, phone, text, date, is_secure, status)
text = msg[2]
date = msg[3]
is_secure = msg[4]
status = msg[5] # sent / recv
tag = f"{status}_{'secure' if is_secure else 'normal'}"
prefix = "🔒 " if is_secure else ""
display_text = f"[{date}]\n{prefix}{text}\n\n"
self.msg_display.insert("end", display_text, tag)
self.msg_display.configure(state="disabled")
self.msg_display.yview("end")
def append_system_msg(self, text):
self.msg_display.configure(state="normal")
self.msg_display.insert("end", f"--- {text} ---\n\n", "system")
self.msg_display.configure(state="disabled")
self.msg_display.yview("end")
def send_message(self):
if not self.current_contact:
return
text = self.msg_entry.get().strip()
if not text:
return
is_secure = self.secure_mode_var.get() == "on"
phone = self.current_contact[1]
# Ask controller to send
self.msg_entry.delete(0, 'end')
self.append_system_msg("Sending...")
self.update() # force UI refresh safely
success = self.controller.send_message(phone, text, is_secure)
if success:
self.refresh_messages()
else:
self.append_system_msg("Failed to send SMS!")
def open_settings(self):
SettingsWindow(self, self.controller)
class SettingsWindow(ctk.CTkToplevel):
def __init__(self, master, controller):
super().__init__(master)
self.controller = controller
self.title("Settings")
self.geometry("500x500")
# Bring to front
self.attributes('-topmost', 1)
self.grid_columnconfigure(0, weight=1)
# My Public Key
lbl1 = ctk.CTkLabel(self, text="My Public Key (Share this to chat securely):")
lbl1.grid(row=0, column=0, padx=20, pady=(20, 5), sticky="w")
self.my_key_box = ctk.CTkTextbox(self, height=100)
self.my_key_box.grid(row=1, column=0, padx=20, pady=5, sticky="ew")
self.my_key_box.insert("1.0", self.controller.get_my_public_key())
self.my_key_box.configure(state="disabled")
# Add / Update Contact
lbl_add = ctk.CTkLabel(self, text="Add/Update Contact:", font=ctk.CTkFont(weight="bold"))
lbl_add.grid(row=2, column=0, padx=20, pady=(30, 5), sticky="w")
self.name_entry = ctk.CTkEntry(self, placeholder_text="Contact Name")
self.name_entry.grid(row=3, column=0, padx=20, pady=5, sticky="ew")
self.phone_entry = ctk.CTkEntry(self, placeholder_text="Phone Number (+989...)")
self.phone_entry.grid(row=4, column=0, padx=20, pady=5, sticky="ew")
lbl2 = ctk.CTkLabel(self, text="Contact's Public Key (Optional but required for secure SMS):")
lbl2.grid(row=5, column=0, padx=20, pady=5, sticky="w")
self.contact_key_box = ctk.CTkTextbox(self, height=100)
self.contact_key_box.grid(row=6, column=0, padx=20, pady=5, sticky="ew")
self.save_btn = ctk.CTkButton(self, text="Save Contact", command=self.save_contact)
self.save_btn.grid(row=7, column=0, padx=20, pady=20)
def save_contact(self):
name = self.name_entry.get().strip()
phone = self.phone_entry.get().strip()
pubkey = self.contact_key_box.get("1.0", 'end-1c').strip()
if not name or not phone:
return
if not pubkey:
pubkey = None
self.controller.save_contact(name, phone, pubkey)
self.master.load_contacts()
self.destroy()
if __name__ == "__main__":
# Mock Controller for standalone GUI test
class MockController:
def get_contacts(self): return [("Alice", "+1234", "key"), ("Bob", "+5678", None)]
def get_contact_info(self, phone): return ("Alice", phone, "key") if phone == "+1234" else ("Bob", phone, None)
def get_messages(self, phone): return [(1, phone, "Hi", "2023-01-01", 0, "recv"), (2, phone, "Secret", "2023-01-01", 1, "sent")]
def get_my_public_key(self): return "MY_PEM_MOCK_DATA"
def send_message(self, *args): return True
def save_contact(self, *args): pass
app = AppGUI(MockController())
app.mainloop()