| 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": ""} |
|
|
| |
| 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") |
|
|
| |
| 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) |
| 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") |
|
|