// ignore_for_file: use_build_context_synchronously import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:expandable/expandable.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/functions/compare_versions.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/functions/format_time.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class ManagementModal extends StatefulWidget { final bool dialog; const ManagementModal({ Key? key, required this.dialog }) : super(key: key); @override State createState() => _ManagementModalState(); } class _ManagementModalState extends State with SingleTickerProviderStateMixin { late AnimationController animationController; late Animation animation; final ExpandableController expandableController = ExpandableController(); @override void initState() { expandableController.addListener(() async { await Future.delayed(const Duration(milliseconds: 200)); if (expandableController.value == false) { animationController.animateTo(0); } else { animationController.animateBack(1); } }); animationController = AnimationController( duration: const Duration(milliseconds: 200), vsync: this, ) ..addListener(() => setState(() => {})); animation = Tween( begin: 0.0, end: 0.5, ).animate(CurvedAnimation( parent: animationController, curve: Curves.easeInOut )); super.initState(); } @override void dispose() { animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final statusProvider = Provider.of(context); final appConfigProvider = Provider.of(context); void updateBlocking({ required bool value, required String filter, int? time }) async { final result = await statusProvider.updateBlocking( block: filter, newStatus: value, time: time ); if (mounted && result != null) { if (result != false) { appConfigProvider.addLog(result); } showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.invalidUsernamePassword, color: Colors.red ); } } void disableWithCountdown(int time) async { updateBlocking(value: false, filter: 'general', time: time); expandableController.toggle(); } Widget mainSwitch() { Widget topRow({required bool legacyMode}) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ if (legacyMode == false) ...[ RotationTransition( turns: animation, child: Icon( Icons.keyboard_arrow_down_rounded, size: 26, color: statusProvider.serverStatus!.generalEnabled == true ? Theme.of(context).colorScheme.onSurfaceVariant : Colors.grey, ), ), const SizedBox(width: 8), ], Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( AppLocalizations.of(context)!.allProtections, style: const TextStyle( fontSize: 18, ), ), if (statusProvider.serverStatus!.timeGeneralDisabled > 0) ...[ const SizedBox(height: 2), if (statusProvider.currentDeadline != null) Text( "${AppLocalizations.of(context)!.remainingTime}: ${formatRemainingSeconds(statusProvider.remainingTime)}" ) ] ], ), ], ), Switch( value: statusProvider.serverStatus!.generalEnabled, onChanged: statusProvider.protectionsManagementProcess.contains('general') == false ? (value) { if (value == false && expandableController.expanded == true && legacyMode == false) { expandableController.toggle(); } updateBlocking( value: value, filter: legacyMode == true ? 'general_legacy' : 'general' ); } : null, ) ] ); } Widget bottomRow() { return Container( height: 40, margin: const EdgeInsets.only(top: 8), child: ListView( scrollDirection: Axis.horizontal, children: [ ActionChip( label: Text(AppLocalizations.of(context)!.seconds(30)), onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true ? () => disableWithCountdown(29000) : null, ), const SizedBox(width: 8), ActionChip( label: Text(AppLocalizations.of(context)!.minute(1)), onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true ? () => disableWithCountdown(59000) : null, ), const SizedBox(width: 8), ActionChip( label: Text(AppLocalizations.of(context)!.minutes(10)), onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true ? () => disableWithCountdown(599000) : null, ), const SizedBox(width: 8), ActionChip( label: Text(AppLocalizations.of(context)!.hour(1)), onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true ? () => disableWithCountdown(3599000) : null, ), const SizedBox(width: 8), ActionChip( label: Text(AppLocalizations.of(context)!.hours(24)), onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true ? () => disableWithCountdown(86399000) : null, ), ], ), ); } return Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: serverVersionIsAhead( currentVersion: statusProvider.serverStatus!.serverVersion, referenceVersion: 'v0.107.28', referenceVersionBeta: 'v0.108.0-b.33' ) == true ? ExpandableNotifier( controller: expandableController, child: Material( color: Colors.transparent, borderRadius: BorderRadius.circular(28), child: InkWell( onTap: statusProvider.serverStatus!.generalEnabled == true && !statusProvider.protectionsManagementProcess.contains('general') ? () => expandableController.toggle() : null, borderRadius: BorderRadius.circular(28), child: Container( padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 12 ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(28), color: Theme.of(context).colorScheme.primary.withOpacity(0.1) ), child: Expandable( theme: const ExpandableThemeData( animationDuration: Duration(milliseconds: 200), fadeCurve: Curves.ease ), collapsed: topRow(legacyMode: false), expanded: Column( children: [ topRow(legacyMode: false), bottomRow(), const SizedBox(height: 8) ], ) ), ), ), ) ) : Material( color: Colors.transparent, borderRadius: BorderRadius.circular(28), child: InkWell( onTap: statusProvider.protectionsManagementProcess.contains('general') == false ? () => updateBlocking( value: !statusProvider.serverStatus!.generalEnabled, filter: 'general_legacy' ) : null, borderRadius: BorderRadius.circular(28), child: Container( padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 12 ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(28), color: Theme.of(context).primaryColor.withOpacity(0.1) ), child: topRow(legacyMode: true) ), ), ) ); } Widget smallSwitch(String label, IconData icon, bool value, Function(bool) onChange, bool disabled) { return Material( color: Colors.transparent, child: InkWell( onTap: disabled == false ? () => onChange(!value) : null, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 44, vertical: 8 ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon( icon, size: 24, color: Theme.of(context).listTileTheme.iconColor, ), const SizedBox(width: 16), Text( label, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w400, color: Theme.of(context).colorScheme.onSurface, ), ), ], ), Switch( value: value, onChanged: disabled == false ? onChange : null, ) ], ), ), ), ); } Widget header() { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.only(top: 24), child: Icon( Icons.shield_rounded, size: 24, color: Theme.of(context).listTileTheme.iconColor ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 16), child: Text( AppLocalizations.of(context)!.manageServer, textAlign: TextAlign.center, style: TextStyle( fontSize: 24, color: Theme.of(context).colorScheme.onSurface, ), ), ), ], ), ], ); } List toggles() { return [ mainSwitch(), Container(height: 10), smallSwitch( AppLocalizations.of(context)!.ruleFiltering, Icons.filter_list_rounded, statusProvider.serverStatus!.filteringEnabled, (value) => updateBlocking(value: value, filter: 'filtering'), statusProvider.protectionsManagementProcess.contains('filtering') ), smallSwitch( AppLocalizations.of(context)!.safeBrowsing, Icons.vpn_lock_rounded, statusProvider.serverStatus!.safeBrowsingEnabled, (value) => updateBlocking(value: value, filter: 'safeBrowsing'), statusProvider.protectionsManagementProcess.contains('safeBrowsing') ), smallSwitch( AppLocalizations.of(context)!.parentalFiltering, Icons.block, statusProvider.serverStatus!.parentalControlEnabled, (value) => updateBlocking(value: value, filter: 'parentalControl'), statusProvider.protectionsManagementProcess.contains('parentalControl') ), smallSwitch( AppLocalizations.of(context)!.safeSearch, Icons.search_rounded, statusProvider.serverStatus!.safeSearchEnabled, (value) => updateBlocking(value: value, filter: 'safeSearch'), statusProvider.protectionsManagementProcess.contains('safeSearch') ), ]; } if (widget.dialog == true) { return Dialog( child: ConstrainedBox( constraints: const BoxConstraints( maxWidth: 400 ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Flexible( child: SingleChildScrollView( child: Wrap( children: [ header(), ...toggles() ], ), ), ), Padding( padding: const EdgeInsets.all(24), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.pop(context), child: Text(AppLocalizations.of(context)!.close), ), ], ), ), if (Platform.isIOS) const SizedBox(height: 16) ], ), ), ); } else { return Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.only( topLeft: Radius.circular(28), topRight: Radius.circular(28) ) ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Flexible( child: SingleChildScrollView( child: Wrap( children: [ header(), ...toggles() ], ), ), ), Padding( padding: const EdgeInsets.all(24), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.pop(context), child: Text(AppLocalizations.of(context)!.close), ), ], ), ), if (Platform.isIOS) const SizedBox(height: 16) ], ), ); } } }