diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index 8e27123..8ba3331 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -1,7 +1,8 @@ // ignore_for_file: use_build_context_synchronously -import 'package:animations/animations.dart'; import 'package:flutter/material.dart'; +import 'package:animations/animations.dart'; +import 'package:bottom_sheet/bottom_sheet.dart'; import 'package:flutter/rendering.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -139,15 +140,21 @@ class _AddedListState extends State { } void openClientModal(Client client) { - showModalBottomSheet( + showFlexibleBottomSheet( + minHeight: 0.6, + initHeight: 0.6, + maxHeight: 0.95, + isCollapsible: true, + duration: const Duration(milliseconds: 250), + anchors: [0.95], context: context, - builder: (ctx) => ClientModal( + builder: (ctx, controller, offset) => ClientModal( + scrollController: controller, client: client, onConfirm: confirmEditClient, onDelete: deleteClient, ), - isScrollControlled: true, - backgroundColor: Colors.transparent + bottomSheetColor: Colors.transparent ); } diff --git a/lib/screens/clients/client_modal.dart b/lib/screens/clients/client_modal.dart index f6c78ad..3b7c030 100644 --- a/lib/screens/clients/client_modal.dart +++ b/lib/screens/clients/client_modal.dart @@ -11,12 +11,14 @@ import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/models/clients.dart'; class ClientModal extends StatefulWidget { + final ScrollController scrollController; final Client? client; final void Function(Client) onConfirm; final void Function(Client)? onDelete; const ClientModal({ Key? key, + required this.scrollController, this.client, required this.onConfirm, this.onDelete, @@ -249,458 +251,453 @@ class _ClientModalState extends State { return Padding( padding: MediaQuery.of(context).viewInsets, - child: DraggableScrollableSheet( - initialChildSize: 0.6, - minChildSize: 0.6, - maxChildSize: 0.95, - builder: (context, scrollController) => Container( - decoration: BoxDecoration( - color: Theme.of(context).dialogBackgroundColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28) - ) - ), - child: Column( - children: [ - Expanded( - child: ListView( - controller: scrollController, - children: [ - const SizedBox(height: 28), - const Icon( - Icons.add, - size: 26, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).dialogBackgroundColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) + ) + ), + child: Column( + children: [ + Expanded( + child: ListView( + controller: widget.scrollController, + children: [ + const SizedBox(height: 28), + const Icon( + Icons.add, + size: 26, + ), + const SizedBox(height: 20), + Text( + AppLocalizations.of(context)!.addClient, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 24 ), - const SizedBox(height: 20), - Text( - AppLocalizations.of(context)!.addClient, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 24 + ), + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: TextFormField( + enabled: widget.client != null ? false : true, + controller: nameController, + onChanged: (_) => checkValidValues(), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.badge_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.name, ), ), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: TextFormField( - enabled: widget.client != null ? false : true, - controller: nameController, - onChanged: (_) => checkValidValues(), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.badge_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) + ), + sectionLabel(AppLocalizations.of(context)!.tags), + Material( + color: Colors.transparent, + child: InkWell( + onTap: editMode == true ? () => openTagsModal() : null, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 20 + ), + child: Row( + children: [ + const Icon( + Icons.label_rounded, + color: Colors.grey, + ), + const SizedBox(width: 20), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.selectTags, + style: const TextStyle( + fontSize: 16, + ), + ), + const SizedBox(height: 5), + Text( + selectedTags.isNotEmpty + ? "${selectedTags.length} ${AppLocalizations.of(context)!.tagsSelected}" + : AppLocalizations.of(context)!.noTagsSelected, + style: const TextStyle( + color: Colors.grey + ), + ) + ], ) - ), - labelText: AppLocalizations.of(context)!.name, + ], ), ), ), - sectionLabel(AppLocalizations.of(context)!.tags), - Material( - color: Colors.transparent, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + sectionLabel(AppLocalizations.of(context)!.identifiers), + if (editMode == true) Padding( + padding: const EdgeInsets.only(right: 20), + child: IconButton( + onPressed: () => setState(() => identifiersControllers.add({ + 'id': uuid.v4(), + 'controller': TextEditingController() + })), + icon: const Icon(Icons.add) + ), + ) + ], + ), + if (identifiersControllers.isNotEmpty) ...identifiersControllers.map((controller) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Padding( + padding: const EdgeInsets.only(bottom: 20), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: editMode == true + ? MediaQuery.of(context).size.width - 108 + : MediaQuery.of(context).size.width - 40, + child: TextFormField( + enabled: editMode, + controller: controller['controller'], + onChanged: (_) => checkValidValues(), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.tag), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + helperText: AppLocalizations.of(context)!.identifierHelper, + labelText: AppLocalizations.of(context)!.identifier, + ), + ), + ), + if (editMode == true) ...[ + const SizedBox(width: 20), + Padding( + padding: const EdgeInsets.only(bottom: 25), + child: IconButton( + onPressed: () => setState( + () => identifiersControllers = identifiersControllers.where((e) => e['id'] != controller['id']).toList() + ), + icon: const Icon(Icons.remove_circle_outline_outlined) + ), + ) + ] + ], + ), + ), + )).toList(), + if (identifiersControllers.isEmpty) Container( + padding: const EdgeInsets.only(top: 10), + child: Text( + AppLocalizations.of(context)!.noIdentifiers, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 18, + color: Colors.grey + ), + ), + ), + sectionLabel(AppLocalizations.of(context)!.settings), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Material( + color: Theme.of(context).primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(28), child: InkWell( - onTap: editMode == true ? () => openTagsModal() : null, + onTap: editMode + ? () => enableDisableGlobalSettingsFiltering() + : null, + borderRadius: BorderRadius.circular(28), child: Padding( padding: const EdgeInsets.symmetric( - vertical: 10, horizontal: 20 + horizontal: 20, + vertical: 5 ), child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Icon( - Icons.label_rounded, - color: Colors.grey, + Text( + AppLocalizations.of(context)!.useGlobalSettings, + style: const TextStyle( + fontSize: 16, + ), ), - const SizedBox(width: 20), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.selectTags, - style: const TextStyle( - fontSize: 16, - ), + Switch( + value: useGlobalSettingsFiltering, + onChanged: editMode == true + ? (value) => enableDisableGlobalSettingsFiltering() + : null, + activeColor: Theme.of(context).primaryColor, + ) + ], + ), + ), + ), + ), + ), + const SizedBox(height: 10), + settignsTile( + label: AppLocalizations.of(context)!.enableFiltering, + value: enableFiltering, + onChange: editMode == true + ? (value) => setState(() => enableFiltering = value) + : null + ), + settignsTile( + label: AppLocalizations.of(context)!.enableSafeBrowsing, + value: enableSafeBrowsing, + onChange: editMode == true + ? (value) => setState(() => enableSafeBrowsing = value) + : null + ), + settignsTile( + label: AppLocalizations.of(context)!.enableParentalControl, + value: enableParentalControl, + onChange: editMode == true + ? (value) => setState(() => enableParentalControl = value) + : null + ), + settignsTile( + label: AppLocalizations.of(context)!.enableSafeSearch, + value: enableSafeSearch, + onChange: editMode == true + ? (value) => setState(() => enableSafeSearch = value) + : null + ), + sectionLabel(AppLocalizations.of(context)!.blockedServices), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Material( + color: Theme.of(context).primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(28), + child: InkWell( + onTap: editMode == true + ? () => updateServicesGlobalSettings(!useGlobalSettingsServices) + : null, + borderRadius: BorderRadius.circular(28), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 5 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.useGlobalSettings, + style: const TextStyle( + fontSize: 16, + ), + ), + Switch( + value: useGlobalSettingsServices, + onChanged: editMode == true + ? (value) => updateServicesGlobalSettings(value) + : null, + activeColor: Theme.of(context).primaryColor, + ) + ], + ), + ), + ), + ), + ), + const SizedBox(height: 10), + Material( + color: Colors.transparent, + child: InkWell( + onTap: editMode == true + ? useGlobalSettingsServices == false + ? openServicesModal + : null + : null, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 20 + ), + child: Row( + children: [ + const Icon( + Icons.public, + color: Colors.grey, + ), + const SizedBox(width: 20), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.selectBlockedServices, + style: TextStyle( + fontSize: 16, + color: useGlobalSettingsServices == false + ? null + : Colors.grey ), + ), + if (useGlobalSettingsServices == false) ...[ const SizedBox(height: 5), Text( - selectedTags.isNotEmpty - ? "${selectedTags.length} ${AppLocalizations.of(context)!.tagsSelected}" - : AppLocalizations.of(context)!.noTagsSelected, + blockedServices.isNotEmpty + ? "${blockedServices.length} ${AppLocalizations.of(context)!.servicesBlocked}" + : AppLocalizations.of(context)!.noBlockedServicesSelected, style: const TextStyle( color: Colors.grey ), ) - ], - ) - ], - ), - ), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - sectionLabel(AppLocalizations.of(context)!.identifiers), - if (editMode == true) Padding( - padding: const EdgeInsets.only(right: 20), - child: IconButton( - onPressed: () => setState(() => identifiersControllers.add({ - 'id': uuid.v4(), - 'controller': TextEditingController() - })), - icon: const Icon(Icons.add) - ), - ) - ], - ), - if (identifiersControllers.isNotEmpty) ...identifiersControllers.map((controller) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Padding( - padding: const EdgeInsets.only(bottom: 20), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - width: editMode == true - ? MediaQuery.of(context).size.width - 108 - : MediaQuery.of(context).size.width - 40, - child: TextFormField( - enabled: editMode, - controller: controller['controller'], - onChanged: (_) => checkValidValues(), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.tag), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - helperText: AppLocalizations.of(context)!.identifierHelper, - labelText: AppLocalizations.of(context)!.identifier, - ), - ), - ), - if (editMode == true) ...[ - const SizedBox(width: 20), - Padding( - padding: const EdgeInsets.only(bottom: 25), - child: IconButton( - onPressed: () => setState( - () => identifiersControllers = identifiersControllers.where((e) => e['id'] != controller['id']).toList() - ), - icon: const Icon(Icons.remove_circle_outline_outlined) - ), - ) - ] - ], - ), - ), - )).toList(), - if (identifiersControllers.isEmpty) Container( - padding: const EdgeInsets.only(top: 10), - child: Text( - AppLocalizations.of(context)!.noIdentifiers, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 18, - color: Colors.grey - ), - ), - ), - sectionLabel(AppLocalizations.of(context)!.settings), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Material( - color: Theme.of(context).primaryColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(28), - child: InkWell( - onTap: editMode - ? () => enableDisableGlobalSettingsFiltering() - : null, - borderRadius: BorderRadius.circular(28), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 5 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.useGlobalSettings, - style: const TextStyle( - fontSize: 16, - ), - ), - Switch( - value: useGlobalSettingsFiltering, - onChanged: editMode == true - ? (value) => enableDisableGlobalSettingsFiltering() - : null, - activeColor: Theme.of(context).primaryColor, - ) + ] ], - ), - ), - ), - ), - ), - const SizedBox(height: 10), - settignsTile( - label: AppLocalizations.of(context)!.enableFiltering, - value: enableFiltering, - onChange: editMode == true - ? (value) => setState(() => enableFiltering = value) - : null - ), - settignsTile( - label: AppLocalizations.of(context)!.enableSafeBrowsing, - value: enableSafeBrowsing, - onChange: editMode == true - ? (value) => setState(() => enableSafeBrowsing = value) - : null - ), - settignsTile( - label: AppLocalizations.of(context)!.enableParentalControl, - value: enableParentalControl, - onChange: editMode == true - ? (value) => setState(() => enableParentalControl = value) - : null - ), - settignsTile( - label: AppLocalizations.of(context)!.enableSafeSearch, - value: enableSafeSearch, - onChange: editMode == true - ? (value) => setState(() => enableSafeSearch = value) - : null - ), - sectionLabel(AppLocalizations.of(context)!.blockedServices), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Material( - color: Theme.of(context).primaryColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(28), - child: InkWell( - onTap: editMode == true - ? () => updateServicesGlobalSettings(!useGlobalSettingsServices) - : null, - borderRadius: BorderRadius.circular(28), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 5 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.useGlobalSettings, - style: const TextStyle( - fontSize: 16, - ), - ), - Switch( - value: useGlobalSettingsServices, - onChanged: editMode == true - ? (value) => updateServicesGlobalSettings(value) - : null, - activeColor: Theme.of(context).primaryColor, - ) - ], - ), - ), - ), - ), - ), - const SizedBox(height: 10), - Material( - color: Colors.transparent, - child: InkWell( - onTap: editMode == true - ? useGlobalSettingsServices == false - ? openServicesModal - : null - : null, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 10, horizontal: 20 - ), - child: Row( - children: [ - const Icon( - Icons.public, - color: Colors.grey, - ), - const SizedBox(width: 20), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.selectBlockedServices, - style: TextStyle( - fontSize: 16, - color: useGlobalSettingsServices == false - ? null - : Colors.grey - ), - ), - if (useGlobalSettingsServices == false) ...[ - const SizedBox(height: 5), - Text( - blockedServices.isNotEmpty - ? "${blockedServices.length} ${AppLocalizations.of(context)!.servicesBlocked}" - : AppLocalizations.of(context)!.noBlockedServicesSelected, - style: const TextStyle( - color: Colors.grey - ), - ) - ] - ], - ) - ], - ), - ), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - sectionLabel(AppLocalizations.of(context)!.upstreamServers), - if (editMode == true) Padding( - padding: const EdgeInsets.only(right: 20), - child: IconButton( - onPressed: () => setState(() => upstreamServers.add({ - 'id': uuid.v4(), - 'controller': TextEditingController() - })), - icon: const Icon(Icons.add) - ), - ) - ], - ), - if (upstreamServers.isNotEmpty) ...upstreamServers.map((controller) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Padding( - padding: const EdgeInsets.only(bottom: 20), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - width: editMode == true - ? MediaQuery.of(context).size.width - 108 - : MediaQuery.of(context).size.width - 40, - child: TextFormField( - enabled: editMode, - controller: controller['controller'], - onChanged: (_) => checkValidValues(), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.dns_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.serverAddress, - ), - ), - ), - if (editMode == true) ...[ - const SizedBox(width: 20), - IconButton( - onPressed: () => setState( - () => upstreamServers = upstreamServers.where((e) => e['id'] != controller['id']).toList() - ), - icon: const Icon(Icons.remove_circle_outline_outlined) - ) - ] - ], - ), - ), - )).toList(), - if (upstreamServers.isEmpty) Container( - padding: const EdgeInsets.only(top: 10), - child: Column( - children: [ - Text( - AppLocalizations.of(context)!.noUpstreamServers, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 18, - color: Colors.grey - ), - ), - const SizedBox(height: 10), - Text( - AppLocalizations.of(context)!.willBeUsedGeneralServers, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 15, - color: Colors.grey - ), - ), - ], - ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(20), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - if (widget.client != null && editMode == false) ...[ - IconButton( - onPressed: () => setState(() => editMode = true), - icon: const Icon(Icons.edit) - ), - const SizedBox(width: 10), - ], - if (widget.client != null && widget.onDelete != null) IconButton( - onPressed: openDeleteClientModal, - icon: const Icon(Icons.delete) - ), - ], - ), - Row( - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) - ), - if (widget.client == null || (widget.client != null && editMode == true)) ...[ - const SizedBox(width: 20), - TextButton( - onPressed: checkValidValues() == true - ? () { - createClient(); - Navigator.pop(context); - } - : null, - child: Text( - widget.client != null && editMode == true - ? AppLocalizations.of(context)!.save - : AppLocalizations.of(context)!.confirm, - style: TextStyle( - color: checkValidValues() == true - ? Theme.of(context).primaryColor - : Colors.grey - ), ) + ], + ), + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + sectionLabel(AppLocalizations.of(context)!.upstreamServers), + if (editMode == true) Padding( + padding: const EdgeInsets.only(right: 20), + child: IconButton( + onPressed: () => setState(() => upstreamServers.add({ + 'id': uuid.v4(), + 'controller': TextEditingController() + })), + icon: const Icon(Icons.add) + ), + ) + ], + ), + if (upstreamServers.isNotEmpty) ...upstreamServers.map((controller) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Padding( + padding: const EdgeInsets.only(bottom: 20), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: editMode == true + ? MediaQuery.of(context).size.width - 108 + : MediaQuery.of(context).size.width - 40, + child: TextFormField( + enabled: editMode, + controller: controller['controller'], + onChanged: (_) => checkValidValues(), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.dns_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.serverAddress, + ), + ), ), - ] + if (editMode == true) ...[ + const SizedBox(width: 20), + IconButton( + onPressed: () => setState( + () => upstreamServers = upstreamServers.where((e) => e['id'] != controller['id']).toList() + ), + icon: const Icon(Icons.remove_circle_outline_outlined) + ) + ] + ], + ), + ), + )).toList(), + if (upstreamServers.isEmpty) Container( + padding: const EdgeInsets.only(top: 10), + child: Column( + children: [ + Text( + AppLocalizations.of(context)!.noUpstreamServers, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 18, + color: Colors.grey + ), + ), + const SizedBox(height: 10), + Text( + AppLocalizations.of(context)!.willBeUsedGeneralServers, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 15, + color: Colors.grey + ), + ), ], - ) - ], - ), + ), + ), + ], ), - ], - ), + ), + Padding( + padding: const EdgeInsets.all(20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + if (widget.client != null && editMode == false) ...[ + IconButton( + onPressed: () => setState(() => editMode = true), + icon: const Icon(Icons.edit) + ), + const SizedBox(width: 10), + ], + if (widget.client != null && widget.onDelete != null) IconButton( + onPressed: openDeleteClientModal, + icon: const Icon(Icons.delete) + ), + ], + ), + Row( + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ), + if (widget.client == null || (widget.client != null && editMode == true)) ...[ + const SizedBox(width: 20), + TextButton( + onPressed: checkValidValues() == true + ? () { + createClient(); + Navigator.pop(context); + } + : null, + child: Text( + widget.client != null && editMode == true + ? AppLocalizations.of(context)!.save + : AppLocalizations.of(context)!.confirm, + style: TextStyle( + color: checkValidValues() == true + ? Theme.of(context).primaryColor + : Colors.grey + ), + ) + ), + ] + ], + ) + ], + ), + ), + ], ), ), ); diff --git a/lib/screens/clients/fab.dart b/lib/screens/clients/fab.dart index 88a0cac..e34c0f1 100644 --- a/lib/screens/clients/fab.dart +++ b/lib/screens/clients/fab.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:bottom_sheet/bottom_sheet.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/clients/client_modal.dart'; @@ -113,13 +114,19 @@ class ClientsFab extends StatelessWidget { } void openAddClient() { - showModalBottomSheet( + showFlexibleBottomSheet( + minHeight: 0.6, + initHeight: 0.6, + maxHeight: 0.95, + isCollapsible: true, + duration: const Duration(milliseconds: 250), + anchors: [0.95], context: context, - builder: (ctx) => ClientModal( + builder: (ctx, controller, offset) => ClientModal( + scrollController: controller, onConfirm: confirmAddClient ), - isScrollControlled: true, - backgroundColor: Colors.transparent + bottomSheetColor: Colors.transparent ); } diff --git a/lib/screens/filters/filters_list.dart b/lib/screens/filters/filters_list.dart index 81cbfb4..6e454e9 100644 --- a/lib/screens/filters/filters_list.dart +++ b/lib/screens/filters/filters_list.dart @@ -2,8 +2,8 @@ import 'dart:io'; -import 'package:adguard_home_manager/screens/filters/delete_list_modal.dart'; import 'package:flutter/material.dart'; +import 'package:bottom_sheet/bottom_sheet.dart'; import 'package:provider/provider.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -11,6 +11,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/filters/fab.dart'; import 'package:adguard_home_manager/screens/filters/list_details_modal.dart'; import 'package:adguard_home_manager/screens/filters/add_list_modal.dart'; +import 'package:adguard_home_manager/screens/filters/delete_list_modal.dart'; import 'package:adguard_home_manager/services/http_requests.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -212,9 +213,16 @@ class _FiltersListState extends State { } void openDetailsModal(Filter filter) { - showModalBottomSheet( + showFlexibleBottomSheet( + minHeight: 0.6, + initHeight: 0.6, + maxHeight: (filter.enabled == true ? 774 : 755)/MediaQuery.of(context).size.height, + isCollapsible: true, + duration: const Duration(milliseconds: 250), + anchors: [(filter.enabled == true ? 774 : 755)/MediaQuery.of(context).size.height], context: context, - builder: (ctx) => ListDetailsModal( + builder: (ctx, controller, offset) => ListDetailsModal( + scrollController: controller, list: filter, type: widget.type, onDelete: (Filter list, String type) { @@ -242,8 +250,7 @@ class _FiltersListState extends State { }, onEnableDisable: enableDisableList, ), - backgroundColor: Colors.transparent, - isScrollControlled: true + bottomSheetColor: Colors.transparent ); } diff --git a/lib/screens/filters/list_details_modal.dart b/lib/screens/filters/list_details_modal.dart index 6da00c4..67c7c41 100644 --- a/lib/screens/filters/list_details_modal.dart +++ b/lib/screens/filters/list_details_modal.dart @@ -7,6 +7,7 @@ import 'package:adguard_home_manager/functions/format_time.dart'; import 'package:adguard_home_manager/models/filtering.dart'; class ListDetailsModal extends StatelessWidget { + final ScrollController scrollController; final Filter list; final String type; final void Function(Filter, String) onDelete; @@ -15,6 +16,7 @@ class ListDetailsModal extends StatelessWidget { const ListDetailsModal({ Key? key, + required this.scrollController, required this.list, required this.type, required this.onDelete, @@ -24,23 +26,22 @@ class ListDetailsModal extends StatelessWidget { @override Widget build(BuildContext context) { - return DraggableScrollableSheet( - initialChildSize: 0.6, - minChildSize: 0.6, - maxChildSize: list.lastUpdated != null - ? 740/MediaQuery.of(context).size.height - : 670/MediaQuery.of(context).size.height, - builder: (context, scrollController) => Container( - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28) - ), - color: Theme.of(context).dialogBackgroundColor + return Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) ), - child: Column( - children: [ - Expanded( + color: Theme.of(context).dialogBackgroundColor + ), + child: Column( + children: [ + Expanded( + child: NotificationListener( + onNotification: (overscroll) { + overscroll.disallowIndicator(); + return false; + }, child: ListView( controller: scrollController, children: [ @@ -126,40 +127,40 @@ class ListDetailsModal extends StatelessWidget { subtitle: formatTimestampUTC(list.lastUpdated!, 'dd-MM-yyyy HH:mm'), ), ], - ) - ), - Padding( - padding: const EdgeInsets.all(20), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - IconButton( - onPressed: () { - Navigator.pop(context); - edit(type); - }, - icon: const Icon(Icons.edit) - ), - const SizedBox(width: 10), - IconButton( - onPressed: () { - onDelete(list, type); - }, - icon: const Icon(Icons.delete) - ), - ], - ), - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.close) - ) - ], ), + ) + ), + Padding( + padding: const EdgeInsets.all(20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + IconButton( + onPressed: () { + Navigator.pop(context); + edit(type); + }, + icon: const Icon(Icons.edit) + ), + const SizedBox(width: 10), + IconButton( + onPressed: () { + onDelete(list, type); + }, + icon: const Icon(Icons.delete) + ), + ], + ), + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.close) + ) + ], ), - ], - ), + ), + ], ), ); } diff --git a/lib/screens/logs/log_details_modal.dart b/lib/screens/logs/log_details_modal.dart index 99e3283..d0a55e9 100644 --- a/lib/screens/logs/log_details_modal.dart +++ b/lib/screens/logs/log_details_modal.dart @@ -8,11 +8,13 @@ import 'package:adguard_home_manager/functions/format_time.dart'; import 'package:adguard_home_manager/models/logs.dart'; class LogDetailsModal extends StatelessWidget { + final ScrollController scrollController; final Log log; final void Function(Log, String) blockUnblock; const LogDetailsModal({ Key? key, + required this.scrollController, required this.log, required this.blockUnblock }) : super(key: key); @@ -30,184 +32,179 @@ class LogDetailsModal extends StatelessWidget { ); } - return DraggableScrollableSheet( - initialChildSize: 0.6, - minChildSize: 0.6, - maxChildSize: 0.95, - builder: (context, controller) => Container( - decoration: BoxDecoration( - color: Theme.of(context).dialogBackgroundColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28), - ) - ), - child: Column( - children: [ - const Padding( - padding: EdgeInsets.only( - top: 24, - bottom: 20, - ), - child: Icon( - Icons.list_rounded, - size: 26, - ), + return Container( + decoration: BoxDecoration( + color: Theme.of(context).dialogBackgroundColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28), + ) + ), + child: Column( + children: [ + const Padding( + padding: EdgeInsets.only( + top: 24, + bottom: 20, ), - Text( - AppLocalizations.of(context)!.logDetails, - style: const TextStyle( - fontSize: 24 - ), + child: Icon( + Icons.list_rounded, + size: 26, ), - Expanded( - child: ListView( - controller: controller, - children: [ - Padding( - padding: const EdgeInsets.all(20), - child: Text( - AppLocalizations.of(context)!.status, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).primaryColor - ), + ), + Text( + AppLocalizations.of(context)!.logDetails, + style: const TextStyle( + fontSize: 24 + ), + ), + Expanded( + child: ListView( + controller: scrollController, + children: [ + Padding( + padding: const EdgeInsets.all(20), + child: Text( + AppLocalizations.of(context)!.status, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).primaryColor ), ), - LogListTile( - icon: Icons.shield_rounded, - title: AppLocalizations.of(context)!.result, - subtitleWidget: getResult(), - trailing: log.cached == true - ? Container( - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - color: Colors.grey, - borderRadius: BorderRadius.circular(10) + ), + LogListTile( + icon: Icons.shield_rounded, + title: AppLocalizations.of(context)!.result, + subtitleWidget: getResult(), + trailing: log.cached == true + ? Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: Colors.grey, + borderRadius: BorderRadius.circular(10) + ), + child: const Text( + "CACHE", + style: TextStyle( + fontSize: 12, + color: Colors.white, + fontWeight: FontWeight.w500 ), - child: const Text( - "CACHE", - style: TextStyle( - fontSize: 12, - color: Colors.white, - fontWeight: FontWeight.w500 - ), - ), - ) - : null, - ), - if (log.rule != null) LogListTile( - icon: Icons.block, - title: AppLocalizations.of(context)!.blockingRule, - subtitle: log.rule - ), - LogListTile( - icon: Icons.schedule, - title: AppLocalizations.of(context)!.time, - subtitle: formatTimestampUTCFromAPI(log.time, 'HH:mm:ss') - ), - Padding( - padding: const EdgeInsets.all(20), - child: Text( - AppLocalizations.of(context)!.request, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).primaryColor - ), - ), - ), - LogListTile( - icon: Icons.domain_rounded, - title: AppLocalizations.of(context)!.domain, - subtitle: log.question.name - ), - LogListTile( - icon: Icons.category_rounded, - title: AppLocalizations.of(context)!.type, - subtitle: log.question.type - ), - LogListTile( - icon: Icons.class_rounded, - title: AppLocalizations.of(context)!.clas, - subtitle: log.question.questionClass - ), - Padding( - padding: const EdgeInsets.all(20), - child: Text( - AppLocalizations.of(context)!.response, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).primaryColor - ), - ), - ), - if (log.upstream != '') LogListTile( - icon: Icons.dns_rounded, - title: AppLocalizations.of(context)!.dnsServer, - subtitle: log.upstream - ), - LogListTile( - icon: Icons.timer_rounded, - title: AppLocalizations.of(context)!.elapsedTime, - subtitle: "${double.parse(log.elapsedMs).toStringAsFixed(2)} ms" - ), - LogListTile( - icon: Icons.system_update_alt_rounded, - title: AppLocalizations.of(context)!.responseCode, - subtitle: log.status - ), - Padding( - padding: const EdgeInsets.all(20), - child: Text( - AppLocalizations.of(context)!.client, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).primaryColor - ), - ), - ), - LogListTile( - icon: Icons.smartphone_rounded, - title: AppLocalizations.of(context)!.deviceIp, - subtitle: log.client - ), - if (log.clientInfo.name != '') LogListTile( - icon: Icons.abc_rounded, - title: AppLocalizations.of(context)!.deviceName, - subtitle: log.clientInfo.name - ), - ], - ) - ), - Padding( - padding: const EdgeInsets.all(20), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: () { - blockUnblock(log, getFilteredStatus(context, log.reason)['filtered'] == true ? 'unblock' : 'block'); - Navigator.pop(context); - }, - child: Text( - getFilteredStatus(context, log.reason)['filtered'] == true - ? AppLocalizations.of(context)!.unblockDomain - : AppLocalizations.of(context)!.blockDomain + ), ) + : null, + ), + if (log.rule != null) LogListTile( + icon: Icons.block, + title: AppLocalizations.of(context)!.blockingRule, + subtitle: log.rule + ), + LogListTile( + icon: Icons.schedule, + title: AppLocalizations.of(context)!.time, + subtitle: formatTimestampUTCFromAPI(log.time, 'HH:mm:ss') + ), + Padding( + padding: const EdgeInsets.all(20), + child: Text( + AppLocalizations.of(context)!.request, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).primaryColor + ), ), - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.close) + ), + LogListTile( + icon: Icons.domain_rounded, + title: AppLocalizations.of(context)!.domain, + subtitle: log.question.name + ), + LogListTile( + icon: Icons.category_rounded, + title: AppLocalizations.of(context)!.type, + subtitle: log.question.type + ), + LogListTile( + icon: Icons.class_rounded, + title: AppLocalizations.of(context)!.clas, + subtitle: log.question.questionClass + ), + Padding( + padding: const EdgeInsets.all(20), + child: Text( + AppLocalizations.of(context)!.response, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).primaryColor + ), ), - ], - ), + ), + if (log.upstream != '') LogListTile( + icon: Icons.dns_rounded, + title: AppLocalizations.of(context)!.dnsServer, + subtitle: log.upstream + ), + LogListTile( + icon: Icons.timer_rounded, + title: AppLocalizations.of(context)!.elapsedTime, + subtitle: "${double.parse(log.elapsedMs).toStringAsFixed(2)} ms" + ), + LogListTile( + icon: Icons.system_update_alt_rounded, + title: AppLocalizations.of(context)!.responseCode, + subtitle: log.status + ), + Padding( + padding: const EdgeInsets.all(20), + child: Text( + AppLocalizations.of(context)!.client, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).primaryColor + ), + ), + ), + LogListTile( + icon: Icons.smartphone_rounded, + title: AppLocalizations.of(context)!.deviceIp, + subtitle: log.client + ), + if (log.clientInfo.name != '') LogListTile( + icon: Icons.abc_rounded, + title: AppLocalizations.of(context)!.deviceName, + subtitle: log.clientInfo.name + ), + ], ) - ], - ), + ), + Padding( + padding: const EdgeInsets.all(20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () { + blockUnblock(log, getFilteredStatus(context, log.reason)['filtered'] == true ? 'unblock' : 'block'); + Navigator.pop(context); + }, + child: Text( + getFilteredStatus(context, log.reason)['filtered'] == true + ? AppLocalizations.of(context)!.unblockDomain + : AppLocalizations.of(context)!.blockDomain + ) + ), + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.close) + ), + ], + ), + ) + ], ), ); } diff --git a/lib/screens/logs/log_tile.dart b/lib/screens/logs/log_tile.dart index a56305f..2a22d44 100644 --- a/lib/screens/logs/log_tile.dart +++ b/lib/screens/logs/log_tile.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:bottom_sheet/bottom_sheet.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/logs/log_details_modal.dart'; @@ -123,14 +124,20 @@ class LogTile extends StatelessWidget { } void openLogDetailsModal() { - showModalBottomSheet( + showFlexibleBottomSheet( + minHeight: 0.6, + initHeight: 0.6, + maxHeight: 0.95, + isCollapsible: true, + duration: const Duration(milliseconds: 250), + anchors: [0.95], context: context, - builder: (ctx) => LogDetailsModal( + builder: (ctx, controller, offset) => LogDetailsModal( + scrollController: controller, log: log, blockUnblock: blockUnblock, ), - backgroundColor: Colors.transparent, - isScrollControlled: true + bottomSheetColor: Colors.transparent, ); } diff --git a/pubspec.lock b/pubspec.lock index 2477528..42e0c0e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -36,6 +36,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + bottom_inset_observer: + dependency: transitive + description: + name: bottom_inset_observer + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + bottom_sheet: + dependency: "direct main" + description: + name: bottom_sheet + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.2" characters: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e3ea09a..6d2c2a3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,7 @@ dependencies: fl_chart: ^0.55.2 flutter_web_browser: ^0.17.1 flutter_svg: ^1.1.5 + bottom_sheet: ^3.1.2 dev_dependencies: flutter_test: