From fe8a02b74206298573da10aa78e8ae254817fe66 Mon Sep 17 00:00:00 2001 From: MOJ1403 Date: Mon, 23 Mar 2026 21:07:08 +0330 Subject: [PATCH] revert color an aicon an better ui and ux --- secure_sms/application/controller.py | 4 + secure_sms/application/services.py | 3 + secure_sms/infrastructure/database.py | 8 + secure_sms/ui/main_window.py | 489 +++++++++++++++++--------- 4 files changed, 339 insertions(+), 165 deletions(-) diff --git a/secure_sms/application/controller.py b/secure_sms/application/controller.py index 8af4180..f7f17e9 100644 --- a/secure_sms/application/controller.py +++ b/secure_sms/application/controller.py @@ -95,6 +95,10 @@ class AppController: self.service.add_or_update_contact(name, phone) self._notify_ui() + def delete_contact(self, phone: str): + self.service.delete_contact(phone) + self._notify_ui() + def get_messages(self, phone: str): return self.service.get_messages(phone) diff --git a/secure_sms/application/services.py b/secure_sms/application/services.py index 738e655..68500a4 100644 --- a/secure_sms/application/services.py +++ b/secure_sms/application/services.py @@ -111,6 +111,9 @@ class SecureMessagingService: def add_or_update_contact(self, name: str, phone: str): self.db.upsert_contact(phone, self._enc(name)) + def delete_contact(self, phone: str): + self.db.delete_contact(phone) + def ensure_contact(self, phone: str, fallback_name: Optional[str] = None): fallback = fallback_name or SYSTEM_CONTACT_LABEL self.db.ensure_contact_exists(phone, self._enc(fallback)) diff --git a/secure_sms/infrastructure/database.py b/secure_sms/infrastructure/database.py index 25feb8f..0351d7a 100644 --- a/secure_sms/infrastructure/database.py +++ b/secure_sms/infrastructure/database.py @@ -200,6 +200,14 @@ class Database: cursor.execute("SELECT * FROM contacts WHERE phone = ?", (phone,)) return cursor.fetchone() + def delete_contact(self, phone: str): + with self._connect() as conn: + conn.execute("DELETE FROM contacts WHERE phone = ?", (phone,)) + conn.execute("DELETE FROM messages WHERE phone = ?", (phone,)) + conn.execute("DELETE FROM packet_fragments WHERE phone = ?", (phone,)) + conn.execute("DELETE FROM secure_events WHERE phone = ?", (phone,)) + conn.commit() + def list_contact_rows(self) -> list[sqlite3.Row]: with self._connect() as conn: cursor = conn.cursor() diff --git a/secure_sms/ui/main_window.py b/secure_sms/ui/main_window.py index 99e9ee8..1b094a0 100644 --- a/secure_sms/ui/main_window.py +++ b/secure_sms/ui/main_window.py @@ -163,6 +163,12 @@ class SecureSmsApp(ctk.CTk): self.option_add("*Cursor", "none") self._build_shell() self.bind_all("", self._handle_global_tap, add="+") + self.bind_all("", self._handle_global_drag_start, add="+") + self.bind_all("", self._handle_global_drag, add="+") + self.bind_all("", self._handle_global_drag_stop, add="+") + self._touch_start_y = None + self._active_scroll_frame = None + self._show_lock_screen() self.after(50, self._enable_touch_kiosk_mode) @@ -258,6 +264,38 @@ class SecureSmsApp(ctk.CTk): return self.after(20, self._hide_virtual_keyboard) + def _find_scrollable_ancestor(self, widget): + current = widget + while current is not None: + if isinstance(current, ctk.CTkScrollableFrame): + return current + current = getattr(current, "master", None) + return None + + def _handle_global_drag_start(self, event): + self._touch_start_y = event.y_root + self._active_scroll_frame = self._find_scrollable_ancestor(event.widget) + + def _handle_global_drag(self, event): + if self._touch_start_y is None or not self._active_scroll_frame: + return + + try: + current_y = event.y_root + delta_y = current_y - self._touch_start_y + + # 1 unit is roughly 15 pixels of drag + units = int(delta_y / 12) + if units != 0: + self._active_scroll_frame._parent_canvas.yview_scroll(-units, "units") + self._touch_start_y = current_y + except Exception: + pass + + def _handle_global_drag_stop(self, event): + self._touch_start_y = None + self._active_scroll_frame = None + def _register_keyboard_interaction(self): self._keyboard_interaction_guard = True self.after(160, self._clear_keyboard_interaction_guard) @@ -647,19 +685,8 @@ class SecureSmsApp(ctk.CTk): self.after(80, lambda: self._focus_registered_input(self.password_entry)) def _configure_root_layout(self): - for column in range(2): - self.root_frame.grid_columnconfigure(column, weight=0, minsize=0) - for row in range(2): - self.root_frame.grid_rowconfigure(row, weight=0, minsize=0) - if self.is_portrait: - top_height = min(max(270, int(self.window_height * 0.36)), 320) - self.root_frame.grid_columnconfigure(0, weight=1) - self.root_frame.grid_rowconfigure(0, weight=0, minsize=top_height) - self.root_frame.grid_rowconfigure(1, weight=1) - else: - self.root_frame.grid_columnconfigure(0, weight=0, minsize=248) - self.root_frame.grid_columnconfigure(1, weight=1) - self.root_frame.grid_rowconfigure(0, weight=1) + self.root_frame.grid_columnconfigure(0, weight=1) + self.root_frame.grid_rowconfigure(0, weight=1) def _configure_profile_card_layout(self): for widget in ( @@ -726,8 +753,6 @@ class SecureSmsApp(ctk.CTk): title_size = 24 if self.is_portrait else 28 subtitle_size = 12 if self.is_portrait else 13 action_height = 46 if self.is_portrait else 42 - main_row = 1 if self.is_portrait else 0 - main_column = 0 if self.is_portrait else 1 self.sidebar = ctk.CTkFrame(self.root_frame, fg_color=SIDEBAR, corner_radius=0, border_width=0) self.sidebar.grid(row=0, column=0, sticky="nsew") @@ -735,162 +760,123 @@ class SecureSmsApp(ctk.CTk): self.sidebar.grid_rowconfigure(5, weight=1) sidebar_header = ctk.CTkFrame(self.sidebar, fg_color="transparent") - sidebar_header.grid(row=0, column=0, padx=14, pady=(12, 2), sticky="ew") + sidebar_header.grid(row=0, column=0, padx=14, pady=(12, 10), sticky="ew") sidebar_header.grid_columnconfigure(0, weight=1) + sidebar_header.grid_columnconfigure(1, weight=0) + + RTLButton( + sidebar_header, + text='☰', + width=40, + height=40, + corner_radius=20, + fg_color="transparent", + hover_color=BORDER, + text_color="white", + font=ctk.CTkFont(family=FONT_TITLE, size=24, weight="bold"), + command=self._show_drawer_menu + ).grid(row=0, column=1, padx=(6, 0), sticky="e") + RTLLabel( sidebar_header, text='صبا', text_color="white", font=ctk.CTkFont(family=FONT_TITLE, size=title_size, weight="bold"), ).grid(row=0, column=0, sticky="e") - RTLLabel( - sidebar_header, - text='پیام\u200cرسان امن', - text_color=PRIMARY_SOFT, - font=ctk.CTkFont(family=FONT_BODY, size=subtitle_size), - ).grid(row=1, column=0, sticky="e") - self.connection_badge = RTLLabel( + self.fab = RTLButton( self.sidebar, - text="", - corner_radius=6, - fg_color="#2E7D62", - text_color="white", - font=ctk.CTkFont(family=FONT_BODY, size=13, weight="bold"), - padx=10, - pady=6, - ) - self.connection_badge.grid(row=2, column=0, padx=14, pady=(6, 8), sticky="ew") - - top_actions = ctk.CTkFrame(self.sidebar, fg_color="transparent") - top_actions.grid(row=3, column=0, padx=14, pady=(0, 6), sticky="ew") - top_actions.grid_columnconfigure((0, 1), weight=1) - RTLButton( - top_actions, - text='گفتگوی جدید', - command=self._open_contact_dialog, + text='+', + width=56, + height=56, + corner_radius=28, fg_color=PRIMARY, - text_color="#FFFFFF", hover_color=PRIMARY_DARK, - corner_radius=8, - height=action_height, - font=ctk.CTkFont(family=FONT_BODY, size=14, weight="bold"), - ).grid(row=0, column=0, padx=4, sticky="ew") - RTLButton( - top_actions, - text='⚙ تنظیمات', - command=self._open_settings_panel, - fg_color=INPUT_BG, - text_color=TEXT, - hover_color=BORDER, - corner_radius=8, - height=action_height, - font=ctk.CTkFont(family=FONT_BODY, size=14, weight="bold"), - ).grid(row=0, column=1, padx=4, sticky="ew") - - self.contact_form_card = ctk.CTkFrame( - self.sidebar, - fg_color=SIDEBAR_SOFT, - corner_radius=10, - border_width=1, - border_color=BORDER, - ) - self.contact_form_card.grid(row=4, column=0, padx=14, pady=(0, 10), sticky="ew") - self.contact_form_card.grid_columnconfigure(0, weight=1) - RTLLabel( - self.contact_form_card, - text='مخاطب جدید', text_color="white", - font=ctk.CTkFont(family=FONT_BODY, size=17, weight="bold"), - ).grid(row=0, column=0, padx=14, pady=(14, 8), sticky="e") - self.contact_name_entry = RTLEntry( - self.contact_form_card, - placeholder_text='نام مخاطب', - height=44, - font=ctk.CTkFont(family=FONT_BODY, size=14 if self.is_portrait else 15), + font=ctk.CTkFont(family=FONT_TITLE, size=32, weight="bold"), + command=self._open_contact_dialog ) - self.contact_name_entry.grid(row=1, column=0, padx=14, pady=6, sticky="ew") - self.contact_phone_entry = RTLEntry( - self.contact_form_card, - placeholder_text='شماره موبایل', - height=44, - font=ctk.CTkFont(family=FONT_BODY, size=14 if self.is_portrait else 15), - ) - self.contact_phone_entry.grid(row=2, column=0, padx=14, pady=6, sticky="ew") - self.contact_form_message = RTLLabel( - self.contact_form_card, - text="", - text_color="#FDE68A", - font=ctk.CTkFont(family=FONT_BODY, size=13), - ) - self.contact_form_message.grid(row=3, column=0, padx=14, pady=(4, 2), sticky="e") - contact_actions = ctk.CTkFrame(self.contact_form_card, fg_color="transparent") - contact_actions.grid(row=4, column=0, padx=10, pady=(4, 14), sticky="ew") - contact_actions.grid_columnconfigure((0, 1), weight=1) + + self.add_contact_panel = ctk.CTkFrame(self.root_frame, fg_color=BACKGROUND, corner_radius=0) + self.add_contact_panel.grid_rowconfigure(1, weight=1) + self.add_contact_panel.grid_columnconfigure(0, weight=1) + + add_contact_header = ctk.CTkFrame(self.add_contact_panel, fg_color=CARD, corner_radius=0, border_width=0) + add_contact_header.grid(row=0, column=0, sticky="ew") + add_contact_header.grid_columnconfigure(0, weight=1) + add_contact_header.grid_columnconfigure(1, weight=0) + + RTLLabel( + add_contact_header, + text='مخاطب جدید', + text_color=TEXT, + font=ctk.CTkFont(family=FONT_TITLE, size=18, weight="bold"), + ).grid(row=0, column=0, padx=16, pady=14, sticky="e") + RTLButton( - contact_actions, + add_contact_header, + text='بازگشت', + width=64, + height=36, + corner_radius=18, + fg_color=INPUT_BG, + hover_color=BORDER, + text_color=TEXT, + font=ctk.CTkFont(family=FONT_BODY, size=14, weight="bold"), + command=self._hide_contact_form + ).grid(row=0, column=1, padx=(4, 14), sticky="e") + + self.contact_form_card = ctk.CTkFrame(self.add_contact_panel, fg_color="transparent") + self.contact_form_card.grid(row=1, column=0, padx=20, pady=20, sticky="nwe") + self.contact_form_card.grid_columnconfigure(0, weight=1) + + self.contact_name_entry = RTLEntry(self.contact_form_card, placeholder_text='نام مخاطب', height=48, font=ctk.CTkFont(family=FONT_BODY, size=16)) + self.contact_name_entry.grid(row=0, column=0, pady=8, sticky="ew") + self.contact_phone_entry = RTLEntry(self.contact_form_card, placeholder_text='شماره موبایل', height=48, font=ctk.CTkFont(family=FONT_BODY, size=16)) + self.contact_phone_entry.grid(row=1, column=0, pady=8, sticky="ew") + self.contact_form_message = RTLLabel(self.contact_form_card, text="", text_color=DANGER, font=ctk.CTkFont(family=FONT_BODY, size=13)) + self.contact_form_message.grid(row=2, column=0, pady=(2, 6), sticky="e") + + RTLButton( + self.contact_form_card, text='ذخیره', fg_color=PRIMARY, - text_color="#FFFFFF", + text_color="white", hover_color=PRIMARY_DARK, command=self._save_contact_inline, - corner_radius=8, - font=ctk.CTkFont(family=FONT_BODY, size=15, weight="bold"), - height=40, - ).grid(row=0, column=0, padx=4, sticky="ew") - RTLButton( - contact_actions, - text='بستن', - fg_color=INPUT_BG, - text_color=TEXT, - hover_color=BORDER, - command=self._hide_contact_form, - corner_radius=8, - font=ctk.CTkFont(family=FONT_BODY, size=15, weight="bold"), - height=40, - ).grid(row=0, column=1, padx=4, sticky="ew") + corner_radius=12, + font=ctk.CTkFont(family=FONT_BODY, size=16, weight="bold"), + height=48, + ).grid(row=3, column=0, pady=10, sticky="ew") + self.contact_phone_entry.bind("", lambda _event: self._save_contact_inline()) self._register_text_input(self.contact_name_entry, title="نام مخاطب", layout="fa") - self._register_text_input( - self.contact_phone_entry, - title="شماره موبایل", - layout="numeric", - submit=self._save_contact_inline, - ) - self.contact_form_card.grid_remove() + self._register_text_input(self.contact_phone_entry, title="شماره موبایل", layout="numeric", submit=self._save_contact_inline) self.contacts_frame = RTLScrollableFrame( self.sidebar, - height=160 if self.is_portrait else 320, + height=300 if self.is_portrait else 320, label_text='گفتگو\u200cها', label_font=ctk.CTkFont(family=FONT_BODY, size=15, weight="bold"), fg_color="transparent", ) - self.contacts_frame.grid(row=5, column=0, padx=8, pady=4, sticky="nsew") + self.contacts_frame.grid(row=1, column=0, padx=8, pady=4, sticky="nsew") + + self.fab.place(relx=0.06, rely=0.94, anchor="sw") + self.fab.lift() self.main_panel = ctk.CTkFrame(self.root_frame, fg_color=BACKGROUND, corner_radius=0) - self.main_panel.grid(row=main_row, column=main_column, sticky="nsew") + self.main_panel.grid(row=0, column=0, sticky="nsew") self.main_panel.grid_rowconfigure(1, weight=1) self.main_panel.grid_columnconfigure(0, weight=1) + self.header_card = ctk.CTkFrame(self.main_panel, fg_color=CARD, corner_radius=0, border_width=0) self.header_card.grid(row=0, column=0, sticky="ew") self.header_card.grid_columnconfigure(0, weight=1) self.header_card.grid_columnconfigure(1, weight=0) - self.chat_title = RTLLabel( - self.header_card, - text='یک مخاطب را انتخاب کن', - text_color=TEXT, - font=ctk.CTkFont(family=FONT_TITLE, size=16 if self.is_portrait else 18, weight="bold"), - ) - self.chat_title.grid(row=0, column=0, padx=16, pady=(10, 2), sticky="e") - self.chat_subtitle = RTLLabel( - self.header_card, - text='در اینجا فقط دو حالت داری: عادی یا امن', - text_color=MUTED, - font=ctk.CTkFont(family=FONT_BODY, size=12), - ) - self.chat_subtitle.grid(row=1, column=0, padx=16, pady=(0, 10), sticky="e") + self.header_card.grid_columnconfigure(2, weight=0) + self.mode_badge = RTLLabel( self.header_card, text='عادی', @@ -901,7 +887,35 @@ class SecureSmsApp(ctk.CTk): pady=6, font=ctk.CTkFont(family=FONT_BODY, size=12, weight="bold"), ) - self.mode_badge.grid(row=0, column=1, rowspan=2, padx=14, sticky="e") + self.mode_badge.grid(row=0, column=0, rowspan=2, padx=14, sticky="w") + + self.chat_title = RTLLabel( + self.header_card, + text='یک مخاطب را انتخاب کن', + text_color=TEXT, + font=ctk.CTkFont(family=FONT_TITLE, size=16 if self.is_portrait else 18, weight="bold"), + ) + self.chat_title.grid(row=0, column=1, padx=8, pady=(10, 2), sticky="e") + self.chat_subtitle = RTLLabel( + self.header_card, + text='در اینجا فقط دو حالت داری: عادی یا امن', + text_color=MUTED, + font=ctk.CTkFont(family=FONT_BODY, size=12), + ) + self.chat_subtitle.grid(row=1, column=1, padx=8, pady=(0, 10), sticky="e") + + RTLButton( + self.header_card, + text='بازگشت', + width=64, + height=36, + corner_radius=18, + fg_color=INPUT_BG, + hover_color=BORDER, + text_color=TEXT, + font=ctk.CTkFont(family=FONT_BODY, size=14, weight="bold"), + command=self._show_home_screen + ).grid(row=0, column=2, rowspan=2, padx=(4, 14), sticky="e") content = ctk.CTkFrame(self.main_panel, fg_color="transparent") content.grid(row=1, column=0, padx=outer_pad, pady=(0, inner_pad), sticky="nsew") @@ -1031,6 +1045,20 @@ class SecureSmsApp(ctk.CTk): self.overlay_frame.grid_remove() self.refresh_all() + self._show_home_screen() + + def _show_home_screen(self): + self.current_contact_phone = None + self._hide_virtual_keyboard() + self.main_panel.grid_remove() + self.sidebar.grid(row=0, column=0, sticky="nsew") + self._refresh_contacts() + + def _show_chat_screen(self): + self._hide_virtual_keyboard() + self.sidebar.grid_remove() + self.main_panel.grid(row=0, column=0, sticky="nsew") + self._refresh_current_chat() def refresh_all(self): self._refresh_connection_badge() @@ -1042,11 +1070,11 @@ class SecureSmsApp(ctk.CTk): self.refresh_all() def _refresh_connection_badge(self): - modem = self.controller.modem_status() - if modem["connected"]: - self.connection_badge.configure(text=f"مودم متصل | {modem['port']}", fg_color="#2E7D62", text_color="white") - else: - self.connection_badge.configure(text=f"مودم آفلاین | {modem['port']}", fg_color="#9A6C3C", text_color="white") + if hasattr(self, 'drawer_modem_label') and self.drawer_modem_label.winfo_exists(): + modem = self.controller.modem_status() + text = f"وضعیت مودم: {'متصل' if modem['connected'] else 'آفلاین'} | پورت: {modem['port']}" + color = "#2E7D62" if modem['connected'] else "#B6465F" + self.drawer_modem_label.configure(text=text, text_color=color) def _refresh_contacts(self): for widget in self.contacts_frame.winfo_children(): @@ -1056,30 +1084,83 @@ class SecureSmsApp(ctk.CTk): RTLLabel( self.contacts_frame, text='هنوز مخاطبی اضافه نشده است.', - text_color="#D5E8E1", + text_color=MUTED, font=ctk.CTkFont(family=FONT_BODY, size=16), ).grid(row=0, column=0, padx=12, pady=12, sticky="e") return + + colors = ["#F44336", "#E91E63", "#9C27B0", "#673AB7", "#3F51B5", "#2196F3", "#00BCD4", "#009688", "#4CAF50", "#FF9800", "#FF5722"] + for index, contact in enumerate(contacts): - selected = self.current_contact_phone == contact.phone - card = RTLButton( - self.contacts_frame, - text=f"{contact.name}\n{contact.phone}\n{contact.last_message_preview or 'آماده گفتگو'}", - anchor="e", - height=72 if self.is_portrait else 80, - corner_radius=8, - command=lambda phone=contact.phone: self._select_contact(phone), - fg_color=SURFACE if selected else "transparent", - hover_color=BORDER, - text_color=TEXT if selected else "white", - font=ctk.CTkFont(family=FONT_BODY, size=14), - ) - card.grid(row=index, column=0, padx=4, pady=2, sticky="ew") + card = ctk.CTkFrame(self.contacts_frame, fg_color="transparent", corner_radius=12) + card.grid(row=index, column=0, padx=4, pady=4, sticky="ew") + self.contacts_frame.grid_columnconfigure(0, weight=1) + card.grid_columnconfigure(0, weight=1) + card.grid_columnconfigure(1, weight=0) + def make_on_press(p=contact.phone, n=contact.name): + def press_handler(e): + self._long_press_triggered = False + self._long_press_timer = self.after(800, lambda: self._trigger_long_press(p, n)) + return press_handler + + def make_on_release(): + def release_handler(e): + if hasattr(self, "_long_press_timer") and self._long_press_timer: + self.after_cancel(self._long_press_timer) + self._long_press_timer = None + return release_handler + + def make_onclick(p=contact.phone): + def click_handler(e): + if getattr(self, "_long_press_triggered", False): + return + self.after(10, lambda: self._select_contact(p)) + return click_handler + + info_frame = ctk.CTkFrame(card, fg_color="transparent") + info_frame.grid(row=0, column=0, sticky="ew", padx=(10, 14), pady=10) + + RTLLabel(info_frame, text=contact.name, text_color=TEXT, font=ctk.CTkFont(family=FONT_BODY, size=16, weight="bold")).pack(anchor="e") + preview = contact.last_message_preview or 'آماده گفتگو' + preview_str = preview if len(preview) < 32 else preview[:29] + "..." + RTLLabel(info_frame, text=preview_str, text_color=MUTED, font=ctk.CTkFont(family=FONT_BODY, size=14)).pack(anchor="e", pady=(4, 0)) + + avatar_color = colors[hash(contact.name or contact.phone) % len(colors)] + avatar_size = 52 + avatar = ctk.CTkFrame(card, fg_color=avatar_color, corner_radius=avatar_size // 2, width=avatar_size, height=avatar_size) + avatar.grid(row=0, column=1, padx=(0, 10), pady=10) + avatar.grid_propagate(False) + + first_letter = (contact.name or contact.phone)[0].upper() + RTLLabel(avatar, text=first_letter, text_color="white", font=ctk.CTkFont(family=FONT_TITLE, size=22, weight="bold")).place(relx=0.5, rely=0.5, anchor="center") + + card.bind("", make_on_press(), add="+") + card.bind("", make_on_release(), add="+") + card.bind("", make_onclick()) + + info_frame.bind("", make_on_press(), add="+") + info_frame.bind("", make_on_release(), add="+") + info_frame.bind("", make_onclick()) + + avatar.bind("", make_on_press(), add="+") + avatar.bind("", make_on_release(), add="+") + avatar.bind("", make_onclick()) + + for child in info_frame.winfo_children(): + child.bind("", make_on_press(), add="+") + child.bind("", make_on_release(), add="+") + child.bind("", make_onclick()) + for child in avatar.winfo_children(): + child.bind("", make_on_press(), add="+") + child.bind("", make_on_release(), add="+") + child.bind("", make_onclick()) + + # Bind hover effects manually if needed, or rely on transparent bg + def _select_contact(self, phone: str): self.current_contact_phone = phone - self._refresh_contacts() - self._refresh_current_chat() + self._show_chat_screen() def _refresh_current_chat(self): if not self.current_contact_phone: @@ -1218,19 +1299,18 @@ class SecureSmsApp(ctk.CTk): self.refresh_all() def _open_contact_dialog(self): - if self.contact_form_card.winfo_ismapped(): - self._hide_contact_form() - return self.contact_form_message.configure(text="") self.contact_name_entry.delete(0, "end") self.contact_phone_entry.delete(0, "end") - self.contact_form_card.grid() + self.sidebar.grid_remove() + self.add_contact_panel.grid(row=0, column=0, sticky="nsew") + self.add_contact_panel.lift() self.after(80, lambda: self._focus_registered_input(self.contact_name_entry)) def _hide_contact_form(self): self._hide_virtual_keyboard() - self.contact_form_card.grid_remove() - self.contact_form_message.configure(text="") + self.add_contact_panel.grid_remove() + self.sidebar.grid(row=0, column=0, sticky="nsew") def _save_contact_inline(self): name = self.contact_name_entry.get().strip() @@ -1245,7 +1325,86 @@ class SecureSmsApp(ctk.CTk): self.contact_phone_entry.delete(0, "end") self._hide_virtual_keyboard() self.refresh_all() - self.after(900, self._hide_contact_form) + self.after(200, self._hide_contact_form) + + def _trigger_long_press(self, phone, name): + self._long_press_triggered = True + self._long_press_timer = None + self._show_delete_contact_dialog(phone, name) + + def _show_delete_contact_dialog(self, phone, name): + self._show_overlay() + header = self._build_overlay_header('حذف مخاطب', f"آیا از حذف گفتگو با {name} مطمئن هستید؟ تمام پیام‌ها پاک خواهد شد.") + header.pack(fill="x", padx=16, pady=(16, 10)) + + body = ctk.CTkFrame(self.overlay_frame, fg_color=SURFACE, corner_radius=22) + body.pack(fill="both", expand=True, padx=16, pady=(0, 16)) + + def confirm_delete(): + self.controller.delete_contact(phone) + self._hide_overlay() + self._show_home_screen() + + RTLButton( + body, + text='بله، حذف کن', + fg_color=DANGER, + text_color="white", + hover_color="#9C3A4D", + font=ctk.CTkFont(family=FONT_BODY, size=16, weight="bold"), + height=48, + command=confirm_delete, + ).pack(fill="x", padx=18, pady=(24, 8)) + + RTLButton( + body, + text='انصراف', + fg_color=INPUT_BG, + text_color=TEXT, + hover_color=BORDER, + font=ctk.CTkFont(family=FONT_BODY, size=16, weight="bold"), + height=48, + command=self._hide_overlay, + ).pack(fill="x", padx=18, pady=(8, 24)) + + def _show_drawer_menu(self): + self._show_overlay() + header = self._build_overlay_header('منو', 'گزینه‌های برنامه') + header.pack(fill="x", padx=16, pady=(16, 10)) + + body = ctk.CTkFrame(self.overlay_frame, fg_color=SURFACE, corner_radius=22) + body.pack(fill="both", expand=True, padx=16, pady=(0, 16)) + + self.drawer_modem_label = RTLLabel( + body, + text="", + font=ctk.CTkFont(family=FONT_BODY, size=15, weight="bold"), + justify="right" + ) + self.drawer_modem_label.pack(anchor="e", padx=18, pady=(24, 14)) + self._refresh_connection_badge() + + RTLButton( + body, + text='تنظیمات', + fg_color=PRIMARY, + text_color="white", + hover_color=PRIMARY_DARK, + font=ctk.CTkFont(family=FONT_BODY, size=16, weight="bold"), + height=48, + command=self._open_settings_panel, + ).pack(fill="x", padx=18, pady=8) + + RTLButton( + body, + text='بخش ادمین', + fg_color=INPUT_BG, + text_color=TEXT, + hover_color=BORDER, + font=ctk.CTkFont(family=FONT_BODY, size=16, weight="bold"), + height=48, + command=self._open_admin_login, + ).pack(fill="x", padx=18, pady=(8, 24)) def _open_settings_panel(self): self._show_overlay()