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
322 lines
8.7 KiB
Dart
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(),
|
|
),
|
|
);
|
|
}
|
|
}
|