import flet as ft from datetime import datetime # قاعدة بيانات المستخدمين (تتحدث عند التسجيل) db = {"admin": "123"} COLORS = { "bg_dark": "#0d1117", "bg_panel": "#161b22", "bg_card": "#21262d", "accent": "#238636", "my_bubble": "#1f4e6e", "other_bubble": "#272e3b", "border": "#30363d", "text_primary": "#e6edf3", "text_muted": "#8b949e", "text_name": "#58a6ff", "danger": "#f85149", "call_btn": "#388bfd", "warning": "#d29922", } def main(page: ft.Page): page.title = "Dardasha 3TX" page.theme_mode = ft.ThemeMode.DARK page.rtl = True page.padding = 0 page.bgcolor = COLORS["bg_dark"] state = {"user": ""} # ── avatar دائري بحرف ──────────────────────────────────── def avatar(name: str, size: int = 36) -> ft.Container: palette = ["#1f6feb","#388bfd","#238636","#d29922", "#8957e5","#db61a2","#f0883e"] bg = palette[sum(ord(c) for c in name) % len(palette)] return ft.Container( content=ft.Text(name[0].upper(), size=int(size * 0.45), weight="bold", color="white"), width=size, height=size, border_radius=size, bgcolor=bg, alignment=ft.alignment.center, ) def now() -> str: return datetime.now().strftime("%H:%M") # ── pubsub ─────────────────────────────────────────────── def on_broadcast(data): t = data.get("t") if t == "c": is_me = data["u"] == state["user"] bubble = ft.Container( content=ft.Column([ ft.Text(data["u"], size=11, weight="bold", color="#a5d6ff" if is_me else COLORS["text_name"]), ft.Text(data["m"], size=15, color=COLORS["text_primary"], selectable=True), ft.Text(data.get("ts", ""), size=10, color=COLORS["text_muted"], text_align=ft.TextAlign.RIGHT), ], spacing=3, tight=True), bgcolor=COLORS["my_bubble"] if is_me else COLORS["other_bubble"], padding=ft.padding.symmetric(horizontal=14, vertical=10), border_radius=ft.border_radius.only( top_left=14, top_right=14, bottom_left=0 if is_me else 4, bottom_right=4 if is_me else 0, ), border=ft.border.all(1, COLORS["border"]), max_width=280, ) chat_list.controls.append( ft.Row( controls=[ft.Container(width=4), bubble] if is_me else [avatar(data["u"]), bubble], alignment=ft.MainAxisAlignment.END if is_me else ft.MainAxisAlignment.START, vertical_alignment=ft.CrossAxisAlignment.END, ) ) elif t == "join": _sys(f"✦ {data['u']} انضمّ للمحادثة") elif t == "leave": _sys(f"✦ {data['u']} غادر المحادثة") elif t == "call" and data["u"] != state["user"]: page.snack_bar = ft.SnackBar( content=ft.Text(f"📞 مكالمة واردة من: {data['u']}", color="white"), bgcolor="#1a3a52", open=True, ) page.update() def _sys(text: str): chat_list.controls.append( ft.Row([ ft.Container( content=ft.Text(text, size=11, color=COLORS["text_muted"], italic=True), padding=ft.padding.symmetric(horizontal=12, vertical=4), bgcolor=COLORS["bg_card"], border_radius=20, ) ], alignment=ft.MainAxisAlignment.CENTER) ) page.update() page.pubsub.subscribe(on_broadcast) # ── شاشة الشات ─────────────────────────────────────────── chat_list = ft.ListView( expand=True, spacing=8, auto_scroll=True, padding=ft.padding.symmetric(horizontal=10, vertical=12), ) msg_in = ft.TextField( hint_text="اكتب رسالتك...", expand=True, border_radius=24, filled=True, fill_color=COLORS["bg_card"], border_color=COLORS["border"], focused_border_color=COLORS["accent"], hint_style=ft.TextStyle(color=COLORS["text_muted"]), text_style=ft.TextStyle(color=COLORS["text_primary"], size=15), content_padding=ft.padding.symmetric(horizontal=18, vertical=12), on_submit=lambda _: send(None), shift_enter=True, ) def send(e): txt = msg_in.value.strip() if not txt: return payload = {"t": "c", "u": state["user"], "m": txt, "ts": now()} page.pubsub.send_all(payload) # ← send_all بدل send_all_on_thread msg_in.value = "" msg_in.focus() page.update() def logout(): page.pubsub.send_all({"t": "leave", "u": state["user"]}) state["user"] = "" chat_list.controls.clear() page.controls.clear() page.add(login_view) page.update() header = ft.Container( content=ft.Row([ ft.Text("💬 3TX Chat", size=18, weight="bold", color=COLORS["text_primary"]), ft.Row([ ft.ElevatedButton( "📞 اتصال", on_click=lambda _: page.pubsub.send_all( {"t": "call", "u": state["user"]}), bgcolor=COLORS["call_btn"], color="white", style=ft.ButtonStyle( shape=ft.RoundedRectangleBorder(radius=20)), ), ft.ElevatedButton( "🚪 خروج", on_click=lambda _: logout(), bgcolor=COLORS["danger"], color="white", style=ft.ButtonStyle( shape=ft.RoundedRectangleBorder(radius=20)), ), ], spacing=8), ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN), padding=ft.padding.symmetric(horizontal=16, vertical=10), bgcolor=COLORS["bg_panel"], border=ft.border.only( bottom=ft.border.BorderSide(1, COLORS["border"])), ) input_row = ft.Container( content=ft.Row([ msg_in, ft.ElevatedButton( "➤", on_click=send, bgcolor=COLORS["accent"], color="white", style=ft.ButtonStyle(shape=ft.CircleBorder()), width=48, height=48, ), ], spacing=10, vertical_alignment=ft.CrossAxisAlignment.CENTER), padding=ft.padding.symmetric(horizontal=12, vertical=10), bgcolor=COLORS["bg_panel"], border=ft.border.only(top=ft.border.BorderSide(1, COLORS["border"])), ) chat_view = ft.Column([ header, ft.Container(chat_list, expand=True, bgcolor=COLORS["bg_dark"]), input_row, ], expand=True, spacing=0) # ── شاشة تسجيل الدخول ──────────────────────────────────── u_in = ft.TextField( label="اسم المستخدم", border_radius=12, filled=True, fill_color=COLORS["bg_card"], border_color=COLORS["border"], focused_border_color=COLORS["accent"], label_style=ft.TextStyle(color=COLORS["text_muted"]), text_style=ft.TextStyle(color=COLORS["text_primary"]), width=320, ) p_in = ft.TextField( label="كلمة المرور", password=True, can_reveal_password=True, border_radius=12, filled=True, fill_color=COLORS["bg_card"], border_color=COLORS["border"], focused_border_color=COLORS["accent"], label_style=ft.TextStyle(color=COLORS["text_muted"]), text_style=ft.TextStyle(color=COLORS["text_primary"]), width=320, on_submit=lambda _: login(None), ) login_err = ft.Text("", color=COLORS["danger"], size=13) def login(e): u = u_in.value.strip() p = p_in.value if not u or not p: login_err.value = "⚠ أدخل اليوزر وكلمة المرور" page.update() return if u in db and db[u] == p: state["user"] = u page.pubsub.send_all({"t": "join", "u": u}) page.controls.clear() page.add(chat_view) else: login_err.value = "⚠ اسم المستخدم أو كلمة المرور غلط" page.update() def go_register(e): page.controls.clear() page.add(register_view) page.update() login_view = ft.Container( content=ft.Column([ ft.Text("💬", size=60), ft.Text("Dardasha 3TX", size=28, weight="bold", color=COLORS["text_primary"]), ft.Text("سجّل دخولك للمتابعة", size=14, color=COLORS["text_muted"]), ft.Container(height=8), u_in, p_in, login_err, ft.Container(height=4), ft.ElevatedButton( "دخول", on_click=login, width=320, height=48, bgcolor=COLORS["accent"], color="white", style=ft.ButtonStyle( shape=ft.RoundedRectangleBorder(radius=12)), ), ft.TextButton( "ما عندك حساب؟ سجّل الآن", on_click=go_register, style=ft.ButtonStyle(color=COLORS["text_name"]), ), ], horizontal_alignment=ft.CrossAxisAlignment.CENTER, spacing=10), expand=True, alignment=ft.alignment.center, bgcolor=COLORS["bg_dark"], padding=ft.padding.symmetric(horizontal=30, vertical=40), ) # ── شاشة إنشاء حساب ────────────────────────────────────── ru_in = ft.TextField( label="اسم المستخدم الجديد", border_radius=12, filled=True, fill_color=COLORS["bg_card"], border_color=COLORS["border"], focused_border_color=COLORS["accent"], label_style=ft.TextStyle(color=COLORS["text_muted"]), text_style=ft.TextStyle(color=COLORS["text_primary"]), width=320, ) rp_in = ft.TextField( label="كلمة المرور", password=True, can_reveal_password=True, border_radius=12, filled=True, fill_color=COLORS["bg_card"], border_color=COLORS["border"], focused_border_color=COLORS["accent"], label_style=ft.TextStyle(color=COLORS["text_muted"]), text_style=ft.TextStyle(color=COLORS["text_primary"]), width=320, ) rp2_in = ft.TextField( label="تأكيد كلمة المرور", password=True, can_reveal_password=True, border_radius=12, filled=True, fill_color=COLORS["bg_card"], border_color=COLORS["border"], focused_border_color=COLORS["accent"], label_style=ft.TextStyle(color=COLORS["text_muted"]), text_style=ft.TextStyle(color=COLORS["text_primary"]), width=320, ) reg_msg = ft.Text("", size=13) def register(e): u = ru_in.value.strip() p = rp_in.value p2 = rp2_in.value if not u or not p or not p2: reg_msg.color = COLORS["danger"] reg_msg.value = "⚠ أكمل جميع الحقول" elif len(u) < 3: reg_msg.color = COLORS["danger"] reg_msg.value = "⚠ اليوزر لازم 3 أحرف على الأقل" elif len(p) < 3: reg_msg.color = COLORS["danger"] reg_msg.value = "⚠ كلمة المرور لازم 3 أحرف على الأقل" elif p != p2: reg_msg.color = COLORS["danger"] reg_msg.value = "⚠ كلمتا المرور ما تطابقتا" elif u in db: reg_msg.color = COLORS["danger"] reg_msg.value = "⚠ اليوزر مأخوذ، اختر اسماً آخر" else: db[u] = p reg_msg.color = COLORS["accent"] reg_msg.value = "✅ تم إنشاء الحساب! ادخل الآن" ru_in.value = rp_in.value = rp2_in.value = "" page.update() def go_login(e): reg_msg.value = "" page.controls.clear() page.add(login_view) page.update() register_view = ft.Container( content=ft.Column([ ft.Text("✨", size=50), ft.Text("إنشاء حساب جديد", size=26, weight="bold", color=COLORS["text_primary"]), ft.Text("انضم لـ 3TX الآن", size=13, color=COLORS["text_muted"]), ft.Container(height=8), ru_in, rp_in, rp2_in, reg_msg, ft.Container(height=4), ft.ElevatedButton( "إنشاء الحساب", on_click=register, width=320, height=48, bgcolor=COLORS["accent"], color="white", style=ft.ButtonStyle( shape=ft.RoundedRectangleBorder(radius=12)), ), ft.TextButton( "عندك حساب؟ سجّل دخول", on_click=go_login, style=ft.ButtonStyle(color=COLORS["text_name"]), ), ], horizontal_alignment=ft.CrossAxisAlignment.CENTER, spacing=10), expand=True, alignment=ft.alignment.center, bgcolor=COLORS["bg_dark"], padding=ft.padding.symmetric(horizontal=30, vertical=40), ) page.add(login_view) if __name__ == "__main__": ft.app(target=main, view=ft.AppView.WEB_BROWSER, port=7860, host="0.0.0.0")