"""应用与主窗口装配。 本文件只负责把窗口、导航和页面拼起来,不写业务逻辑。 改动本文件前必读 `client/AGENTS.md`。以下几条最容易踩: - 图标一律用 `FluentIcon`,**不得使用表情符号充当图标**。 - 不得在这里发起 Admin 请求、SQLite 长查询或 uiautomator2 调用, 这些会卡死 Qt 主线程。业务事件放到各页面的 `*_ui_event.py`。 - 页面的 `objectName` 必须唯一且稳定,改名会破坏导航。 - 用布局和伸缩项排版,**不要用固定坐标**。 - 不要用全局 QSS 覆盖 Fluent 控件;局部样式必须保留悬停、按下、 焦点和禁用四种状态。 - 需要文件路径时走 `data_dir()` / `app_dir()`,不要硬编码, 见 `docs/client/03-data-model.md` §2.2。 """ import sys from PyQt5.QtCore import Qt, QTimer from PyQt5.QtWidgets import QApplication from qfluentwidgets import ( FluentIcon as FIF, FluentWindow, MessageBox, NavigationItemPosition, Theme, setTheme, ) from .adb_runtime import BundledAdbError, configure_bundled_adb_environment from .pdd_ui import PDDTaskPage from .pdd_ui_event import PDDTaskPageEvent from .pdd_u2_purchase_adapter import ( create_u2_live_purchase_adapter, create_u2_purchase_adapter, ) from .pdd_u2_purchase_reconcile_adapter import ( create_u2_purchase_reconcile_adapter, ) from .settings_ui import SettingsPage from .task_repository import TaskRepository from .update_service import mark_current_version_healthy class MainWindow(FluentWindow): """应用主窗口。""" def __init__( self, task_repository=None, settings_repository=None, admin_gateway=None, ): super().__init__() self.pddTaskPage = PDDTaskPage(self) self.settingsPage = SettingsPage( self, settings_repository=settings_repository, admin_gateway=admin_gateway, live_purchase_adapter_ready=True, ) self.pddTaskPageEvent = PDDTaskPageEvent( self.pddTaskPage, task_repository or TaskRepository(), self, claim_gateway=admin_gateway, settings_repository=settings_repository, purchase_adapter_factory=create_u2_purchase_adapter, live_purchase_adapter_factory=create_u2_live_purchase_adapter, purchase_reconcile_factory=create_u2_purchase_reconcile_adapter, ) self.pddTaskPage.openSettingsRequested.connect( lambda: self.switchTo(self.settingsPage) ) self.settingsPage.eventBinder.androidDeviceConfigurationChanged.connect( self.pddTaskPageEvent.android_device_configuration_changed ) self.settingsPage.eventBinder.adminBaseUrlChanged.connect( self.pddTaskPageEvent.update_admin_base_url ) self.addSubInterface(self.pddTaskPage, FIF.HOME, "pdd") self.settingsNavigationItem = self.addSubInterface( self.settingsPage, FIF.SETTING, "设置", NavigationItemPosition.BOTTOM, ) self._settings_navigation_text = self.settingsNavigationItem.text() self._settings_navigation_light_color = ( self.settingsNavigationItem.lightTextColor ) self._settings_navigation_dark_color = ( self.settingsNavigationItem.darkTextColor ) self.settingsPage.eventBinder.updateEventBinder.updateAvailabilityChanged.connect( self._set_update_navigation_reminder ) self.setWindowTitle("采集采购工具") self.setMinimumSize(820, 560) self.resize(1040, 680) self._moveToScreenCenter() self.pddTaskPageEvent.load_initial_tasks() def _set_update_navigation_reminder( self, available: bool, current_version: str, latest_version: str, ) -> None: """用颜色和文字共同提醒更新,不让提示只依赖颜色。""" if available: self.settingsNavigationItem.setText( f"{self._settings_navigation_text}(有更新)" ) self.settingsNavigationItem.setTextColor("#B45309", "#FBBF24") self.settingsNavigationItem.setToolTip( f"发现新版本 {latest_version},当前版本 {current_version}" ) return self.settingsNavigationItem.setText(self._settings_navigation_text) self.settingsNavigationItem.setTextColor( self._settings_navigation_light_color, self._settings_navigation_dark_color, ) self.settingsNavigationItem.setToolTip(self._settings_navigation_text) def _moveToScreenCenter(self): screen = QApplication.primaryScreen() if screen is None: return frame = self.frameGeometry() frame.moveCenter(screen.availableGeometry().center()) self.move(frame.topLeft()) def ui_main(): """启动桌面应用。""" QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) app = QApplication(sys.argv) setTheme(Theme.AUTO) adb_error = "" try: configure_bundled_adb_environment() except BundledAdbError as exc: adb_error = str(exc) window = MainWindow() window.showMaximized() if adb_error: QTimer.singleShot( 0, lambda: _show_adb_missing_dialog(window, adb_error), ) try: mark_current_version_healthy() except OSError: # 健康标记失败不能让界面崩溃;Launcher 会保留旧版本并在下次启动恢复。 pass return app.exec_() def _show_adb_missing_dialog(parent: MainWindow, message: str) -> None: """在主窗口中央提示内置 ADB 缺失,用户可用大按钮关闭。""" dialog = MessageBox("Android 调试组件缺失", message, parent) dialog.yesButton.setText("知道了") dialog.yesButton.setMinimumSize(120, 40) dialog.cancelButton.hide() dialog.exec() if __name__ == "__main__": sys.exit(ui_main())