xworkmate-app/lib/features/settings/settings_page_sections.dart

538 lines
19 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ignore_for_file: unused_import, unnecessary_import
import 'dart:async';
import 'package:flutter/material.dart';
import '../../app/app_controller.dart';
import '../../app/app_metadata.dart';
import '../../app/app_store_policy.dart';
import '../../app/ui_feature_manifest.dart';
import '../../app/workspace_navigation.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/gateway_runtime.dart';
import '../../runtime/runtime_controllers.dart';
import '../../runtime/runtime_models.dart';
import 'codex_integration_card.dart';
import 'skill_directory_authorization_card.dart';
import '../../widgets/section_tabs.dart';
import '../../widgets/settings_page_shell.dart';
import '../../widgets/surface_card.dart';
import '../../widgets/top_bar.dart';
import 'settings_page_core.dart';
import 'settings_page_gateway.dart';
import 'settings_page_gateway_connection.dart';
import 'settings_page_gateway_llm.dart';
import 'settings_page_presentation.dart';
import 'settings_page_multi_agent.dart';
import 'settings_page_support.dart';
import 'settings_page_device.dart';
import 'settings_page_widgets.dart';
extension SettingsPageSectionsMixinInternal on SettingsPageStateInternal {
List<SettingsTab> orderedOverviewTabsInternal(
AppController controller,
UiFeatureAccess uiFeatures,
) {
final availableTabs = uiFeatures.availableSettingsTabs;
final current = uiFeatures.sanitizeSettingsTab(controller.settingsTab);
return <SettingsTab>[
current,
...availableTabs.where((item) => item != current),
];
}
List<Widget> buildOverviewContentInternal(
BuildContext context,
AppController controller,
SettingsSnapshot settings,
UiFeatureAccess uiFeatures,
) {
return buildOrderedSettingsSections(
availableTabs: uiFeatures.availableSettingsTabs,
currentTab: uiFeatures.sanitizeSettingsTab(controller.settingsTab),
buildTabContent: (tab) => switch (tab) {
SettingsTab.general => buildGeneralInternal(
context,
controller,
settings,
uiFeatures,
),
SettingsTab.workspace => buildWorkspaceInternal(
context,
controller,
settings,
),
SettingsTab.gateway => buildGatewayInternal(
context,
controller,
settings,
uiFeatures,
),
SettingsTab.agents => buildAgentsInternal(
context,
controller,
settings,
),
SettingsTab.appearance => buildAppearanceInternal(context, controller),
SettingsTab.diagnostics => buildDiagnosticsInternal(
context,
controller,
),
SettingsTab.experimental => buildExperimentalInternal(
context,
controller,
settings,
uiFeatures,
),
SettingsTab.about => buildAboutInternal(context, controller),
},
);
}
List<Widget> buildContentForCurrentStateInternal(
BuildContext context,
AppController controller,
SettingsSnapshot settings,
UiFeatureAccess uiFeatures,
) {
if (detailInternal != null) {
return buildDetailContentInternal(
context,
controller,
settings,
uiFeatures,
detailInternal!,
);
}
return switch (tabInternal) {
SettingsTab.general => buildGeneralInternal(
context,
controller,
settings,
uiFeatures,
),
SettingsTab.workspace => buildWorkspaceInternal(
context,
controller,
settings,
),
SettingsTab.gateway => buildGatewayInternal(
context,
controller,
settings,
uiFeatures,
),
SettingsTab.agents => buildAgentsInternal(context, controller, settings),
SettingsTab.appearance => buildAppearanceInternal(context, controller),
SettingsTab.diagnostics => buildDiagnosticsInternal(context, controller),
SettingsTab.experimental => buildExperimentalInternal(
context,
controller,
settings,
uiFeatures,
),
SettingsTab.about => buildAboutInternal(context, controller),
};
}
List<Widget> buildDetailContentInternal(
BuildContext context,
AppController controller,
SettingsSnapshot settings,
UiFeatureAccess uiFeatures,
SettingsDetailPage detail,
) {
return switch (detail) {
SettingsDetailPage.gatewayConnection => <Widget>[
buildDetailIntroInternal(
context,
title: detail.label,
description: appText(
'集中编辑 Gateway 连接、设备配对和会话级连接入口。',
'Edit gateway connection, device pairing, and session-level connection entry points in one place.',
),
),
const SizedBox(height: 16),
buildOpenClawGatewayCardInternal(context, controller, settings),
if (uiFeatures.supportsVaultServer) ...[
const SizedBox(height: 16),
buildVaultProviderCardInternal(context, controller, settings),
],
const SizedBox(height: 16),
buildLlmEndpointManagerInternal(context, controller, settings),
],
SettingsDetailPage.aiGatewayIntegration => <Widget>[
buildDetailIntroInternal(
context,
title: detail.label,
description: appText(
'把主 LLM API 与可选兼容端点统一收口成接入点列表。默认先显示主接入点,需要时可通过 + 扩展更多端点。',
'Manage the primary LLM API and optional compatible endpoints from one endpoint list. Start with the primary entry and expand more endpoints with + when needed.',
),
),
const SizedBox(height: 16),
buildLlmEndpointManagerInternal(context, controller, settings),
],
SettingsDetailPage.vaultProvider => <Widget>[
buildDetailIntroInternal(
context,
title: detail.label,
description: appText(
'在这里维护 Vault 服务地址、可选 namespace以及只进入安全存储的 root token。',
'Maintain the Vault server URL, optional namespace, and the root token that only persists in secure storage here.',
),
),
const SizedBox(height: 16),
if (uiFeatures.supportsVaultServer)
buildVaultProviderCardInternal(context, controller, settings)
else
SurfaceCard(
child: Text(
appText(
'当前发布配置未开放 Vault Server 参数。',
'Vault Server settings are disabled in this release configuration.',
),
),
),
],
SettingsDetailPage.externalAgents => <Widget>[
buildDetailIntroInternal(
context,
title: detail.label,
description: appText(
'多 Agent 协作、角色编排和外部 Agent / ACP 连接的详细参数集中在这里。',
'Detailed multi-agent collaboration, role orchestration, and external Agent / ACP connection settings are edited here.',
),
),
const SizedBox(height: 16),
...buildAgentsInternal(context, controller, settings),
const SizedBox(height: 16),
CodexIntegrationCard(controller: controller),
],
SettingsDetailPage.diagnosticsAdvanced => <Widget>[
buildDetailIntroInternal(
context,
title: detail.label,
description: appText(
'高级诊断集中展示网关诊断、运行日志和设备信息。',
'Advanced diagnostics centralize gateway diagnostics, runtime logs, and device information.',
),
),
const SizedBox(height: 16),
...buildDiagnosticsInternal(context, controller),
],
};
}
Widget buildDetailIntroInternal(
BuildContext context, {
required String title,
required String description,
}) {
return SurfaceCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 10),
Text(description, style: Theme.of(context).textTheme.bodyMedium),
],
),
);
}
Widget buildGlobalApplyBarInternal(
BuildContext context,
AppController controller,
) {
final hasDraft = controller.hasSettingsDraftChanges;
final hasPendingApply = controller.hasPendingSettingsApply;
final message = controller.settingsDraftStatusMessage;
return SettingsGlobalApplyCard(
title: appText('设置提交流程', 'Settings Submission'),
message: message.isNotEmpty
? message
: hasDraft
? appText(
'当前存在未保存草稿。保存并生效:按当前配置立即更新。',
'There are unsaved drafts. Save & apply updates the current configuration immediately.',
)
: hasPendingApply
? appText(
'当前存在待生效更改。保存并生效:立即按当前配置更新。',
'There are saved changes waiting to be applied. Save & apply updates the current configuration immediately.',
)
: appText('当前没有待提交更改。', 'There are no pending settings changes.'),
applyLabel: appText('保存并生效', 'Save & apply'),
onApply: (!hasDraft && !hasPendingApply)
? null
: () => handleTopLevelApplyInternal(controller),
);
}
List<Widget> buildGeneralInternal(
BuildContext context,
AppController controller,
SettingsSnapshot settings,
UiFeatureAccess uiFeatures,
) {
return [
SurfaceCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Application', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 16),
SwitchRowInternal(
label: appText('启用工作台外壳', 'Active workspace shell'),
value: settings.appActive,
onChanged: (value) => saveSettingsInternal(
controller,
settings.copyWith(appActive: value),
),
),
SwitchRowInternal(
label: appText('开机启动', 'Launch at login'),
value: settings.launchAtLogin,
onChanged: (value) => saveSettingsInternal(
controller,
settings.copyWith(launchAtLogin: value),
),
),
SwitchRowInternal(
label: controller.supportsDesktopIntegration
? appText('显示托盘图标', 'Show tray icon')
: appText('显示 Dock 图标', 'Show dock icon'),
value: settings.showDockIcon,
onChanged: (value) => saveSettingsInternal(
controller,
settings.copyWith(showDockIcon: value),
),
),
],
),
),
if (controller.supportsDesktopIntegration)
buildLinuxDesktopIntegrationInternal(context, controller, settings),
];
}
Widget buildLinuxDesktopIntegrationInternal(
BuildContext context,
AppController controller,
SettingsSnapshot settings,
) {
final desktop = controller.desktopIntegration;
final config = settings.linuxDesktop;
final theme = Theme.of(context);
return SurfaceCard(
key: const ValueKey('linux-desktop-integration-card'),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appText('Linux 桌面集成', 'Linux Desktop Integration'),
style: theme.textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
appText(
'统一管理 GNOME / KDE 的代理模式、隧道连接、托盘菜单与开机自启。',
'Manage GNOME / KDE proxy mode, tunnel session, tray menu, and autostart from one surface.',
),
style: theme.textTheme.bodyMedium,
),
const SizedBox(height: 16),
InfoRowInternal(
label: appText('桌面环境', 'Desktop'),
value: desktop.environment.label,
),
InfoRowInternal(
label: 'NetworkManager',
value: desktop.networkManagerAvailable
? appText('可用', 'Available')
: appText('不可用', 'Unavailable'),
),
InfoRowInternal(
label: appText('当前模式', 'Current Mode'),
value: desktop.mode.label,
),
InfoRowInternal(
label: appText('隧道状态', 'Tunnel'),
value: desktop.tunnel.connected
? appText('已连接', 'Connected')
: desktop.tunnel.available
? appText('可连接', 'Ready')
: appText('未检测到配置', 'No profile detected'),
),
InfoRowInternal(
label: appText('系统代理', 'System Proxy'),
value: desktop.systemProxy.enabled
? '${desktop.systemProxy.host}:${desktop.systemProxy.port}'
: appText('未启用', 'Disabled'),
),
SwitchRowInternal(
label: appText('开机启动', 'Launch at login'),
value: settings.launchAtLogin,
onChanged: (value) => saveSettingsInternal(
controller,
settings.copyWith(launchAtLogin: value),
),
),
SwitchRowInternal(
label: appText('托盘菜单', 'Tray menu'),
value: config.trayEnabled,
onChanged: (value) => saveSettingsInternal(
controller,
settings.copyWith(
linuxDesktop: config.copyWith(trayEnabled: value),
),
),
),
EditableFieldInternal(
label: appText('隧道连接名称', 'Tunnel Connection Name'),
value: config.vpnConnectionName,
onSubmitted: (value) => saveSettingsInternal(
controller,
settings.copyWith(
linuxDesktop: config.copyWith(vpnConnectionName: value.trim()),
),
),
),
Row(
children: [
Expanded(
child: EditableFieldInternal(
label: appText('代理主机', 'Proxy Host'),
value: config.proxyHost,
onSubmitted: (value) => saveSettingsInternal(
controller,
settings.copyWith(
linuxDesktop: config.copyWith(proxyHost: value.trim()),
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: EditableFieldInternal(
label: appText('代理端口', 'Proxy Port'),
value: config.proxyPort.toString(),
onSubmitted: (value) {
final parsed = int.tryParse(value.trim());
if (parsed == null || parsed <= 0) {
return;
}
saveSettingsInternal(
controller,
settings.copyWith(
linuxDesktop: config.copyWith(proxyPort: parsed),
),
);
},
),
),
],
),
const SizedBox(height: 6),
Wrap(
spacing: 10,
runSpacing: 10,
children: [
FilledButton.tonal(
onPressed: controller.desktopPlatformBusy
? null
: () => controller.setDesktopVpnMode(VpnMode.proxy),
child: Text(appText('切换到代理', 'Use Proxy')),
),
FilledButton.tonal(
onPressed: controller.desktopPlatformBusy
? null
: () => controller.setDesktopVpnMode(VpnMode.tunnel),
child: Text(appText('切换到隧道', 'Use Tunnel')),
),
OutlinedButton(
onPressed: controller.desktopPlatformBusy
? null
: controller.connectDesktopTunnel,
child: Text(appText('连接隧道', 'Connect Tunnel')),
),
OutlinedButton(
onPressed: controller.desktopPlatformBusy
? null
: controller.disconnectDesktopTunnel,
child: Text(appText('断开隧道', 'Disconnect Tunnel')),
),
OutlinedButton(
onPressed: controller.desktopPlatformBusy
? null
: controller.refreshDesktopIntegration,
child: Text(appText('刷新状态', 'Refresh Status')),
),
],
),
if (desktop.statusMessage.trim().isNotEmpty) ...[
const SizedBox(height: 16),
buildNoticeInternal(
context,
tone: theme.colorScheme.surfaceContainerHighest,
title: appText('桌面状态', 'Desktop Status'),
message: desktop.statusMessage,
),
],
],
),
);
}
List<Widget> buildWorkspaceInternal(
BuildContext context,
AppController controller,
SettingsSnapshot settings,
) {
return [
SurfaceCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appText('工作区', 'Workspace'),
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
EditableFieldInternal(
label: appText('工作区路径', 'Workspace Path'),
value: settings.workspacePath,
onSubmitted: (value) => controller.saveSettingsDraft(
settings.copyWith(workspacePath: value),
),
),
EditableFieldInternal(
label: appText('CLI 路径', 'CLI Path'),
value: settings.cliPath,
onSubmitted: (value) => controller.saveSettingsDraft(
settings.copyWith(cliPath: value),
),
),
EditableFieldInternal(
label: appText('默认模型', 'Default Model'),
value: settings.defaultModel,
onSubmitted: (value) => controller.saveSettingsDraft(
settings.copyWith(defaultModel: value),
),
),
EditableFieldInternal(
label: appText('默认提供方', 'Default Provider'),
value: settings.defaultProvider,
onSubmitted: (value) => controller.saveSettingsDraft(
settings.copyWith(defaultProvider: value),
),
),
],
),
),
];
}
}