Files
MWClash/lib/fragments/tools.dart
chen08209 676f2d058a Add windows server mode start process verify
Add linux deb dependencies

Add backup recovery strategy select

Support custom text scaling

Optimize the display of different text scale

Optimize windows setup experience

Optimize startTun performance

Optimize android tv experience

Optimize default option

Optimize computed text size

Optimize hyperOS freeform window

Add developer mode

Update core

Optimize more details
2025-05-01 00:02:29 +08:00

322 lines
8.7 KiB
Dart

import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/fragments/about.dart';
import 'package:fl_clash/fragments/access.dart';
import 'package:fl_clash/fragments/application_setting.dart';
import 'package:fl_clash/fragments/config/config.dart';
import 'package:fl_clash/fragments/hotkey.dart';
import 'package:fl_clash/l10n/l10n.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import 'backup_and_recovery.dart';
import 'developer.dart';
import 'theme.dart';
import 'package:path/path.dart' show dirname, join;
class ToolsFragment extends ConsumerStatefulWidget {
const ToolsFragment({super.key});
@override
ConsumerState<ToolsFragment> createState() => _ToolboxFragmentState();
}
class _ToolboxFragmentState extends ConsumerState<ToolsFragment> {
_buildNavigationMenuItem(NavigationItem navigationItem) {
return ListItem.open(
leading: navigationItem.icon,
title: Text(Intl.message(navigationItem.label.name)),
subtitle: navigationItem.description != null
? Text(Intl.message(navigationItem.description!))
: null,
delegate: OpenDelegate(
title: Intl.message(navigationItem.label.name),
widget: navigationItem.fragment,
),
);
}
Widget _buildNavigationMenu(List<NavigationItem> navigationItems) {
return Column(
children: [
for (final navigationItem in navigationItems) ...[
_buildNavigationMenuItem(navigationItem),
navigationItems.last != navigationItem
? const Divider(
height: 0,
)
: Container(),
]
],
);
}
List<Widget> _getOtherList(bool enableDeveloperMode) {
return generateSection(
title: appLocalizations.other,
items: [
_DisclaimerItem(),
if (enableDeveloperMode) _DeveloperItem(),
_InfoItem(),
],
);
}
_getSettingList() {
return generateSection(
title: appLocalizations.settings,
items: [
_LocaleItem(),
_ThemeItem(),
_BackupItem(),
if (system.isDesktop) _HotkeyItem(),
if (Platform.isWindows) _LoopbackItem(),
if (Platform.isAndroid) _AccessItem(),
_ConfigItem(),
_SettingItem(),
],
);
}
@override
Widget build(BuildContext context) {
final vm2 = ref.watch(
appSettingProvider.select(
(state) => VM2(a: state.locale, b: state.developerMode),
),
);
final items = [
Consumer(
builder: (_, ref, __) {
final state = ref.watch(moreToolsSelectorStateProvider);
if (state.navigationItems.isEmpty) {
return Container();
}
return Column(
children: [
ListHeader(title: appLocalizations.more),
_buildNavigationMenu(state.navigationItems)
],
);
},
),
..._getSettingList(),
..._getOtherList(vm2.b),
];
return ListView.builder(
itemCount: items.length,
itemBuilder: (_, index) => items[index],
padding: const EdgeInsets.only(bottom: 20),
);
}
}
class _LocaleItem extends ConsumerWidget {
const _LocaleItem();
String _getLocaleString(Locale? locale) {
if (locale == null) return appLocalizations.defaultText;
return Intl.message(locale.toString());
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final locale =
ref.watch(appSettingProvider.select((state) => state.locale));
final subTitle = locale ?? appLocalizations.defaultText;
final currentLocale = utils.getLocaleForString(locale);
return ListItem<Locale?>.options(
leading: const Icon(Icons.language_outlined),
title: Text(appLocalizations.language),
subtitle: Text(Intl.message(subTitle)),
delegate: OptionsDelegate(
title: appLocalizations.language,
options: [null, ...AppLocalizations.delegate.supportedLocales],
onChanged: (Locale? locale) {
ref.read(appSettingProvider.notifier).updateState(
(state) => state.copyWith(locale: locale?.toString()),
);
},
textBuilder: (locale) => _getLocaleString(locale),
value: currentLocale,
),
);
}
}
class _ThemeItem extends StatelessWidget {
const _ThemeItem();
@override
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.style),
title: Text(appLocalizations.theme),
subtitle: Text(appLocalizations.themeDesc),
delegate: OpenDelegate(
title: appLocalizations.theme,
widget: const ThemeFragment(),
),
);
}
}
class _BackupItem extends StatelessWidget {
const _BackupItem();
@override
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.cloud_sync),
title: Text(appLocalizations.backupAndRecovery),
subtitle: Text(appLocalizations.backupAndRecoveryDesc),
delegate: OpenDelegate(
title: appLocalizations.backupAndRecovery,
widget: const BackupAndRecovery(),
),
);
}
}
class _HotkeyItem extends StatelessWidget {
const _HotkeyItem();
@override
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.keyboard),
title: Text(appLocalizations.hotkeyManagement),
subtitle: Text(appLocalizations.hotkeyManagementDesc),
delegate: OpenDelegate(
title: appLocalizations.hotkeyManagement,
widget: const HotKeyFragment(),
),
);
}
}
class _LoopbackItem extends StatelessWidget {
const _LoopbackItem();
@override
Widget build(BuildContext context) {
return ListItem(
leading: const Icon(Icons.lock),
title: Text(appLocalizations.loopback),
subtitle: Text(appLocalizations.loopbackDesc),
onTap: () {
windows?.runas(
'"${join(dirname(Platform.resolvedExecutable), "EnableLoopback.exe")}"',
"",
);
},
);
}
}
class _AccessItem extends StatelessWidget {
const _AccessItem();
@override
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.view_list),
title: Text(appLocalizations.accessControl),
subtitle: Text(appLocalizations.accessControlDesc),
delegate: OpenDelegate(
title: appLocalizations.appAccessControl,
widget: const AccessFragment(),
),
);
}
}
class _ConfigItem extends StatelessWidget {
const _ConfigItem();
@override
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.edit),
title: Text(appLocalizations.basicConfig),
subtitle: Text(appLocalizations.basicConfigDesc),
delegate: OpenDelegate(
title: appLocalizations.override,
widget: const ConfigFragment(),
),
);
}
}
class _SettingItem extends StatelessWidget {
const _SettingItem();
@override
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.settings),
title: Text(appLocalizations.application),
subtitle: Text(appLocalizations.applicationDesc),
delegate: OpenDelegate(
title: appLocalizations.application,
widget: const ApplicationSettingFragment(),
),
);
}
}
class _DisclaimerItem extends StatelessWidget {
const _DisclaimerItem();
@override
Widget build(BuildContext context) {
return ListItem(
leading: const Icon(Icons.gavel),
title: Text(appLocalizations.disclaimer),
onTap: () async {
final isDisclaimerAccepted =
await globalState.appController.showDisclaimer();
if (!isDisclaimerAccepted) {
globalState.appController.handleExit();
}
},
);
}
}
class _InfoItem extends StatelessWidget {
const _InfoItem();
@override
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.info),
title: Text(appLocalizations.about),
delegate: OpenDelegate(
title: appLocalizations.about,
widget: const AboutFragment(),
),
);
}
}
class _DeveloperItem extends StatelessWidget {
const _DeveloperItem();
@override
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.developer_board),
title: Text(appLocalizations.developerMode),
delegate: OpenDelegate(
title: appLocalizations.developerMode,
widget: const DeveloperView(),
),
);
}
}