revert color an aicon an better ui and ux
This commit is contained in:
parent
ee1bebcc90
commit
fe8a02b742
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -163,6 +163,12 @@ class SecureSmsApp(ctk.CTk):
|
|||
self.option_add("*Cursor", "none")
|
||||
self._build_shell()
|
||||
self.bind_all("<Button-1>", self._handle_global_tap, add="+")
|
||||
self.bind_all("<ButtonPress-1>", self._handle_global_drag_start, add="+")
|
||||
self.bind_all("<B1-Motion>", self._handle_global_drag, add="+")
|
||||
self.bind_all("<ButtonRelease-1>", 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,18 +685,7 @@ 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)
|
||||
|
||||
def _configure_profile_card_layout(self):
|
||||
|
|
@ -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("<Return>", 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):
|
||||
if hasattr(self, 'drawer_modem_label') and self.drawer_modem_label.winfo_exists():
|
||||
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")
|
||||
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("<ButtonPress-1>", make_on_press(), add="+")
|
||||
card.bind("<ButtonRelease-1>", make_on_release(), add="+")
|
||||
card.bind("<Button-1>", make_onclick())
|
||||
|
||||
info_frame.bind("<ButtonPress-1>", make_on_press(), add="+")
|
||||
info_frame.bind("<ButtonRelease-1>", make_on_release(), add="+")
|
||||
info_frame.bind("<Button-1>", make_onclick())
|
||||
|
||||
avatar.bind("<ButtonPress-1>", make_on_press(), add="+")
|
||||
avatar.bind("<ButtonRelease-1>", make_on_release(), add="+")
|
||||
avatar.bind("<Button-1>", make_onclick())
|
||||
|
||||
for child in info_frame.winfo_children():
|
||||
child.bind("<ButtonPress-1>", make_on_press(), add="+")
|
||||
child.bind("<ButtonRelease-1>", make_on_release(), add="+")
|
||||
child.bind("<Button-1>", make_onclick())
|
||||
for child in avatar.winfo_children():
|
||||
child.bind("<ButtonPress-1>", make_on_press(), add="+")
|
||||
child.bind("<ButtonRelease-1>", make_on_release(), add="+")
|
||||
child.bind("<Button-1>", 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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user