import 'dart:io'; import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/providers/config.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'; class VPNItem extends ConsumerWidget { const VPNItem({super.key}); @override Widget build(BuildContext context, ref) { final enable = ref.watch(vpnSettingProvider.select((state) => state.enable)); return ListItem.switchItem( title: const Text("VPN"), subtitle: Text(appLocalizations.vpnEnableDesc), delegate: SwitchDelegate( value: enable, onChanged: (value) async { ref.read(vpnSettingProvider.notifier).updateState( (state) => state.copyWith( enable: value, ), ); }, ), ); } } class TUNItem extends ConsumerWidget { const TUNItem({super.key}); @override Widget build(BuildContext context, ref) { final enable = ref.watch(patchClashConfigProvider.select((state) => state.tun.enable)); return ListItem.switchItem( title: Text(appLocalizations.tun), subtitle: Text(appLocalizations.tunDesc), delegate: SwitchDelegate( value: enable, onChanged: (value) async { ref.read(patchClashConfigProvider.notifier).updateState( (state) => state.copyWith.tun( enable: value, ), ); }, ), ); } } class AllowBypassItem extends ConsumerWidget { const AllowBypassItem({super.key}); @override Widget build(BuildContext context, ref) { final allowBypass = ref.watch(vpnSettingProvider.select((state) => state.allowBypass)); return ListItem.switchItem( title: Text(appLocalizations.allowBypass), subtitle: Text(appLocalizations.allowBypassDesc), delegate: SwitchDelegate( value: allowBypass, onChanged: (bool value) async { ref.read(vpnSettingProvider.notifier).updateState( (state) => state.copyWith( allowBypass: value, ), ); }, ), ); } } class VpnSystemProxyItem extends ConsumerWidget { const VpnSystemProxyItem({super.key}); @override Widget build(BuildContext context, ref) { final systemProxy = ref.watch(vpnSettingProvider.select((state) => state.systemProxy)); return ListItem.switchItem( title: Text(appLocalizations.systemProxy), subtitle: Text(appLocalizations.systemProxyDesc), delegate: SwitchDelegate( value: systemProxy, onChanged: (bool value) async { ref.read(vpnSettingProvider.notifier).updateState( (state) => state.copyWith( systemProxy: value, ), ); }, ), ); } } class SystemProxyItem extends ConsumerWidget { const SystemProxyItem({super.key}); @override Widget build(BuildContext context, ref) { final systemProxy = ref.watch(networkSettingProvider.select((state) => state.systemProxy)); return ListItem.switchItem( title: Text(appLocalizations.systemProxy), subtitle: Text(appLocalizations.systemProxyDesc), delegate: SwitchDelegate( value: systemProxy, onChanged: (bool value) async { ref.read(networkSettingProvider.notifier).updateState( (state) => state.copyWith( systemProxy: value, ), ); }, ), ); } } class Ipv6Item extends ConsumerWidget { const Ipv6Item({super.key}); @override Widget build(BuildContext context, ref) { final ipv6 = ref.watch(vpnSettingProvider.select((state) => state.ipv6)); return ListItem.switchItem( title: const Text("IPv6"), subtitle: Text(appLocalizations.ipv6InboundDesc), delegate: SwitchDelegate( value: ipv6, onChanged: (bool value) async { ref.read(vpnSettingProvider.notifier).updateState( (state) => state.copyWith( ipv6: value, ), ); }, ), ); } } class TunStackItem extends ConsumerWidget { const TunStackItem({super.key}); @override Widget build(BuildContext context, ref) { final stack = ref.watch(patchClashConfigProvider.select((state) => state.tun.stack)); return ListItem.options( title: Text(appLocalizations.stackMode), subtitle: Text(stack.name), delegate: OptionsDelegate( value: stack, options: TunStack.values, textBuilder: (value) => value.name, onChanged: (value) { if (value == null) { return; } ref.read(patchClashConfigProvider.notifier).updateState( (state) => state.copyWith.tun( stack: value, ), ); }, title: appLocalizations.stackMode, ), ); } } class BypassDomainItem extends StatelessWidget { const BypassDomainItem({super.key}); _initActions(BuildContext context, WidgetRef ref) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { context.commonScaffoldState?.actions = [ IconButton( onPressed: () async { final res = await globalState.showMessage( title: appLocalizations.reset, message: TextSpan( text: appLocalizations.resetTip, ), ); if (res != true) { return; } ref.read(networkSettingProvider.notifier).updateState( (state) => state.copyWith( bypassDomain: defaultBypassDomain, ), ); }, tooltip: appLocalizations.reset, icon: const Icon( Icons.replay, ), ) ]; }); } @override Widget build(BuildContext context) { return ListItem.open( title: Text(appLocalizations.bypassDomain), subtitle: Text(appLocalizations.bypassDomainDesc), delegate: OpenDelegate( blur: false, title: appLocalizations.bypassDomain, widget: Consumer( builder: (_, ref, __) { _initActions(context, ref); final bypassDomain = ref.watch( networkSettingProvider.select((state) => state.bypassDomain)); return ListInputPage( title: appLocalizations.bypassDomain, items: bypassDomain, titleBuilder: (item) => Text(item), onChange: (items) { ref.read(networkSettingProvider.notifier).updateState( (state) => state.copyWith( bypassDomain: List.from(items), ), ); }, ); }, ), ), ); } } class RouteModeItem extends ConsumerWidget { const RouteModeItem({super.key}); @override Widget build(BuildContext context, ref) { final routeMode = ref.watch(networkSettingProvider.select((state) => state.routeMode)); return ListItem.options( title: Text(appLocalizations.routeMode), subtitle: Text(Intl.message("routeMode_${routeMode.name}")), delegate: OptionsDelegate( title: appLocalizations.routeMode, options: RouteMode.values, onChanged: (RouteMode? value) { if (value == null) { return; } ref.read(networkSettingProvider.notifier).updateState( (state) => state.copyWith( routeMode: value, ), ); }, textBuilder: (routeMode) => Intl.message( "routeMode_${routeMode.name}", ), value: routeMode, ), ); } } class RouteAddressItem extends ConsumerWidget { const RouteAddressItem({super.key}); @override Widget build(BuildContext context, ref) { final bypassPrivate = ref.watch(networkSettingProvider .select((state) => state.routeMode == RouteMode.bypassPrivate)); if (bypassPrivate) { return Container(); } return ListItem.open( title: Text(appLocalizations.routeAddress), subtitle: Text(appLocalizations.routeAddressDesc), delegate: OpenDelegate( blur: false, maxWidth: 360, title: appLocalizations.routeAddress, widget: Consumer( builder: (_, ref, __) { final routeAddress = ref.watch( patchClashConfigProvider.select( (state) => state.tun.routeAddress, ), ); return ListInputPage( title: appLocalizations.routeAddress, items: routeAddress, titleBuilder: (item) => Text(item), onChange: (items) { ref.read(patchClashConfigProvider.notifier).updateState( (state) => state.copyWith.tun( routeAddress: List.from(items), ), ); }, ); }, ), ), ); } } final networkItems = [ if (Platform.isAndroid) const VPNItem(), if (Platform.isAndroid) ...generateSection( title: "VPN", items: [ const VpnSystemProxyItem(), const BypassDomainItem(), const AllowBypassItem(), const Ipv6Item(), ], ), if (system.isDesktop) ...generateSection( title: appLocalizations.system, items: [ SystemProxyItem(), BypassDomainItem(), ], ), ...generateSection( title: appLocalizations.options, items: [ if (system.isDesktop) const TUNItem(), const TunStackItem(), const RouteModeItem(), const RouteAddressItem(), ], ), ]; class NetworkListView extends ConsumerWidget { const NetworkListView({super.key}); _initActions(BuildContext context, WidgetRef ref) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { context.commonScaffoldState?.actions = [ IconButton( onPressed: () async { final res = await globalState.showMessage( title: appLocalizations.reset, message: TextSpan( text: appLocalizations.resetTip, ), ); if (res != true) { return; } ref.read(vpnSettingProvider.notifier).updateState( (state) => defaultVpnProps.copyWith( accessControl: state.accessControl, ), ); ref.read(patchClashConfigProvider.notifier).updateState( (state) => state.copyWith( tun: defaultTun, ), ); }, tooltip: appLocalizations.reset, icon: const Icon( Icons.replay, ), ) ]; }); } @override Widget build(BuildContext context, ref) { _initActions(context, ref); return generateListView( networkItems, ); } }