from __future__ import annotations import asyncio import flet as ft from vpn_native_bridge import NativeBridge from app.models import PaymentInfo from app.screens.safe_area import safe_area from app.screens.settings import build_settings_screen from app.screens.vpn import build_vpn_screen from app.screens.white_lists import build_white_lists_screen from app.services.billing import month_key, payment_banner from app.services.controller import RuntimeController from app.services.settings_registry import SettingsRegistry from app.services.state_store import StateStore from app.theme import apply_accent def main(page: ft.Page) -> None: page.title = "VPN App" page.padding = 0 page.scroll = ft.ScrollMode.AUTO page.theme_mode = ft.ThemeMode.SYSTEM store = StateStore() persisted = store.load() native_bridge = ( NativeBridge() if page.platform in {ft.PagePlatform.ANDROID, ft.PagePlatform.ANDROID_TV} else None ) controller = RuntimeController(native_bridge=native_bridge) registry = SettingsRegistry() payment = PaymentInfo( monthly_price_rub=int(persisted.get("monthly_price_rub", 0)), payment_day=int(persisted.get("payment_day", 1)), ) banner = ft.Ref[ft.Banner]() def dismiss_payment(_: ft.ControlEvent) -> None: state = store.load() state["payment_banner_dismissed"] = month_key() store.save(state) page.close(banner.current) def maybe_show_payment() -> None: if payment.monthly_price_rub <= 0: return if persisted.get("payment_banner_dismissed") == month_key(): return message = payment_banner(payment) if message: page.open( ft.Banner( ref=banner, leading=ft.Icon(ft.Icons.INFO_OUTLINE), content=ft.Text(message), actions=[ ft.TextButton("Не показывать в этом месяце", on_click=dismiss_payment) ], ) ) def set_theme(key: str) -> None: apply_accent(page, key) vpn_screen = build_vpn_screen(page, controller, registry, set_theme) white_lists_screen = build_white_lists_screen( page, controller, registry, lambda _: set_theme("freeturn") ) settings_screen = build_settings_screen(page, registry) def open_settings(_: ft.ControlEvent) -> None: dialog = ft.AlertDialog( modal=True, title=ft.Text("Настройки"), content=ft.Container(settings_screen, width=460, height=520), actions=[ft.TextButton("Закрыть", on_click=lambda _: page.close(dialog))], ) page.open(dialog) def tab_changed(event: ft.ControlEvent) -> None: index = event.control.selected_index set_theme("freeturn" if index == 1 else "basic") page.appbar = ft.AppBar( title=ft.Text("VPN App"), actions=[ft.IconButton(ft.Icons.SETTINGS_OUTLINED, on_click=open_settings)], ) tabs = ft.Tabs( content=ft.Column( [ ft.TabBar( tabs=[ft.Tab(label="VPN"), ft.Tab(label="Белые списки")], scrollable=False, ), ft.TabBarView( controls=[vpn_screen, white_lists_screen], expand=True, ), ], expand=True, ), length=2, expand=True, selected_index=0, on_change=tab_changed, ) page.add( safe_area( ft.Column( [tabs], expand=True, ) ) ) apply_accent(page, "basic") maybe_show_payment() if native_bridge is not None: async def restore_native_state() -> None: while True: await controller.refresh() if controller.engine_kind.value == "freeturn": tabs.selected_index = 1 set_theme("freeturn") page.update() await asyncio.sleep(2) page.run_task(restore_native_state) if __name__ == "__main__": ft.run(main)