import 'dart:io'; import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/controller.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/views/about.dart'; import 'package:fl_clash/views/access.dart'; import 'package:fl_clash/views/application_setting.dart'; import 'package:fl_clash/views/backup_and_restore.dart'; import 'package:fl_clash/views/config/config.dart'; import 'package:fl_clash/views/hotkey.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 'package:path/path.dart' show dirname, join; import 'config/advanced.dart'; import 'developer.dart'; import 'theme.dart'; class ToolsView extends ConsumerStatefulWidget { const ToolsView({super.key}); @override ConsumerState createState() => _ToolViewState(); } class _ToolViewState extends ConsumerState { Widget _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(widget: navigationItem.builder(context)), ); } Widget _buildNavigationMenu(List navigationItems) { return Column( children: [ for (final navigationItem in navigationItems) ...[ _buildNavigationMenuItem(navigationItem), navigationItems.last != navigationItem ? const Divider(height: 0) : Container(), ], ], ); } List _getOtherList(bool enableDeveloperMode) { return generateSection( title: context.appLocalizations.other, items: [ _DisclaimerItem(), if (enableDeveloperMode) _DeveloperItem(), _InfoItem(), ], ); } List _getSettingList() { return generateSection( title: context.appLocalizations.settings, items: [ const _LocaleItem(), const _ThemeItem(), const _BackupItem(), if (system.isDesktop) const _HotkeyItem(), if (system.isWindows) const _LoopbackItem(), if (system.isAndroid) const _AccessItem(), const _ConfigItem(), const _AdvancedConfigItem(), const _SettingItem(), ], ); } @override Widget build(BuildContext context) { final vm2 = ref.watch( appSettingProvider.select( (state) => VM2(state.locale, state.developerMode), ), ); final items = [ Consumer( builder: (_, ref, _) { final state = ref.watch(moreToolsSelectorStateProvider); if (state.navigationItems.isEmpty) { return Container(); } return Column( children: [ ListHeader(title: context.appLocalizations.more), _buildNavigationMenu(state.navigationItems), ], ); }, ), ..._getSettingList(), ..._getOtherList(vm2.b), ]; return CommonScaffold( title: context.appLocalizations.tools, body: ListView.builder( key: toolsStoreKey, 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 ?? context.appLocalizations.defaultText; final currentLocale = utils.getLocaleForString(locale); return ListItem.options( leading: const Icon(Icons.language_outlined), title: Text(context.appLocalizations.language), subtitle: Text(Intl.message(subTitle)), delegate: OptionsDelegate( title: context.appLocalizations.language, options: [null, ...AppLocalizations.delegate.supportedLocales], onChanged: (Locale? locale) { ref .read(appSettingProvider.notifier) .update((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(context.appLocalizations.theme), subtitle: Text(context.appLocalizations.themeDesc), delegate: OpenDelegate(widget: const ThemeView()), ); } } class _BackupItem extends StatelessWidget { const _BackupItem(); @override Widget build(BuildContext context) { return ListItem.open( leading: const Icon(Icons.cloud_sync), title: Text(context.appLocalizations.backupAndRestore), subtitle: Text(context.appLocalizations.backupAndRestoreDesc), delegate: OpenDelegate(widget: const BackupAndRestore()), ); } } class _HotkeyItem extends StatelessWidget { const _HotkeyItem(); @override Widget build(BuildContext context) { return ListItem.open( leading: const Icon(Icons.keyboard), title: Text(context.appLocalizations.hotkeyManagement), subtitle: Text(context.appLocalizations.hotkeyManagementDesc), delegate: OpenDelegate(widget: const HotKeyView()), ); } } class _LoopbackItem extends StatelessWidget { const _LoopbackItem(); @override Widget build(BuildContext context) { return ListItem( leading: const Icon(Icons.lock), title: Text(context.appLocalizations.loopback), subtitle: Text(context.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(context.appLocalizations.accessControl), subtitle: Text(context.appLocalizations.accessControlDesc), delegate: OpenDelegate(widget: const AccessView()), ); } } class _ConfigItem extends StatelessWidget { const _ConfigItem(); @override Widget build(BuildContext context) { return ListItem.open( leading: const Icon(Icons.edit), title: Text(context.appLocalizations.basicConfig), subtitle: Text(context.appLocalizations.basicConfigDesc), delegate: OpenDelegate(widget: const ConfigView()), ); } } class _AdvancedConfigItem extends StatelessWidget { const _AdvancedConfigItem(); @override Widget build(BuildContext context) { return ListItem.open( leading: const Icon(Icons.build), title: Text(context.appLocalizations.advancedConfig), subtitle: Text(context.appLocalizations.advancedConfigDesc), delegate: OpenDelegate(widget: const AdvancedConfigView()), ); } } class _SettingItem extends StatelessWidget { const _SettingItem(); @override Widget build(BuildContext context) { return ListItem.open( leading: const Icon(Icons.settings), title: Text(context.appLocalizations.application), subtitle: Text(context.appLocalizations.applicationDesc), delegate: OpenDelegate(widget: const ApplicationSettingView()), ); } } class _DisclaimerItem extends StatelessWidget { const _DisclaimerItem(); @override Widget build(BuildContext context) { return ListItem( leading: const Icon(Icons.gavel), title: Text(context.appLocalizations.disclaimer), onTap: () async { final isDisclaimerAccepted = await appController.showDisclaimer(); if (!isDisclaimerAccepted) { appController.handleExit(); } }, ); } } class _InfoItem extends StatelessWidget { const _InfoItem(); @override Widget build(BuildContext context) { return ListItem.open( leading: const Icon(Icons.info), title: Text(context.appLocalizations.about), delegate: OpenDelegate(widget: const AboutView()), ); } } class _DeveloperItem extends StatelessWidget { const _DeveloperItem(); @override Widget build(BuildContext context) { return ListItem.open( leading: const Icon(Icons.developer_board), title: Text(context.appLocalizations.developerMode), delegate: OpenDelegate(widget: const DeveloperView()), ); } }