Changed system navigation bar color

This commit is contained in:
Juan Gilsanz Polo 2023-12-09 04:04:14 +01:00
parent 83ea589187
commit b164d520db
47 changed files with 3370 additions and 3207 deletions

View file

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart';
@ -35,6 +36,7 @@ import 'package:adguard_home_manager/services/db/database.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
setWindowMinSize(const Size(500, 500)); setWindowMinSize(const Size(500, 500));

View file

@ -185,34 +185,36 @@ class _ClientScreenState extends State<ClientScreen> {
), ),
actions: actions(), actions: actions(),
), ),
body: ClientForm( body: SafeArea(
isFullScreen: true, child: ClientForm(
client: widget.client, isFullScreen: true,
nameController: nameController, client: widget.client,
updateValidValues: (v) => setState(() => validValues = v), nameController: nameController,
identifiersControllers: identifiersControllers, updateValidValues: (v) => setState(() => validValues = v),
selectedTags: selectedTags, identifiersControllers: identifiersControllers,
useGlobalSettingsFiltering: useGlobalSettingsFiltering, selectedTags: selectedTags,
enableFiltering: enableFiltering, useGlobalSettingsFiltering: useGlobalSettingsFiltering,
enableParentalControl: enableParentalControl, enableFiltering: enableFiltering,
enableSafeBrowsing: enableSafeBrowsing, enableParentalControl: enableParentalControl,
enableSafeSearch: enableSafeSearch, enableSafeBrowsing: enableSafeBrowsing,
safeSearch: safeSearch, enableSafeSearch: enableSafeSearch,
blockedServices: blockedServices, safeSearch: safeSearch,
updateBlockedServices: (v) => setState(() => blockedServices = v), blockedServices: blockedServices,
upstreamServers: upstreamServers, updateBlockedServices: (v) => setState(() => blockedServices = v),
updateUpstreamServers: (v) => setState(() => upstreamServers = v), upstreamServers: upstreamServers,
defaultSafeSearch: defaultSafeSearch, updateUpstreamServers: (v) => setState(() => upstreamServers = v),
useGlobalSettingsServices: useGlobalSettingsServices, defaultSafeSearch: defaultSafeSearch,
updateSelectedTags: (v) => setState(() => selectedTags = v), useGlobalSettingsServices: useGlobalSettingsServices,
updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v), updateSelectedTags: (v) => setState(() => selectedTags = v),
enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering, updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v),
updateEnableFiltering: (v) => setState(() => enableFiltering = v), enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering,
updateEnableParentalControl: (v) => setState(() => enableParentalControl = v), updateEnableFiltering: (v) => setState(() => enableFiltering = v),
updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v), updateEnableParentalControl: (v) => setState(() => enableParentalControl = v),
updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v), updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v),
updateSafeSearch: (v) => setState(() => safeSearch = v), updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v),
updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v), updateSafeSearch: (v) => setState(() => safeSearch = v),
updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v),
),
), ),
), ),
); );

View file

@ -149,125 +149,127 @@ class _LogsListClientState extends State<LogsListClient> {
] ]
], ],
), ),
body: Builder( body: SafeArea(
builder: (context) { child: Builder(
switch (loadStatus) { builder: (context) {
case 0: switch (loadStatus) {
return SizedBox( case 0:
width: double.maxFinite, return SizedBox(
child: Column( width: double.maxFinite,
mainAxisAlignment: MainAxisAlignment.center, child: Column(
crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ crossAxisAlignment: CrossAxisAlignment.center,
const CircularProgressIndicator(), children: [
const SizedBox(height: 30), const CircularProgressIndicator(),
Text( const SizedBox(height: 30),
AppLocalizations.of(context)!.loadingLogs, Text(
textAlign: TextAlign.center, AppLocalizations.of(context)!.loadingLogs,
style: TextStyle( textAlign: TextAlign.center,
fontSize: 22, style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 22,
), color: Theme.of(context).colorScheme.onSurfaceVariant,
) ),
], )
), ],
); ),
);
case 1: case 1:
if (logsData!.data.isNotEmpty) { if (logsData!.data.isNotEmpty) {
return RefreshIndicator( return RefreshIndicator(
onRefresh: fetchLogs, onRefresh: fetchLogs,
child: ListView.builder( child: ListView.builder(
controller: scrollController, controller: scrollController,
padding: const EdgeInsets.only(top: 0), padding: const EdgeInsets.only(top: 0),
itemCount: isLoadingMore == true itemCount: isLoadingMore == true
? logsData!.data.length+1 ? logsData!.data.length+1
: logsData!.data.length, : logsData!.data.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (isLoadingMore == true && index == logsData!.data.length) { if (isLoadingMore == true && index == logsData!.data.length) {
return const Padding( return const Padding(
padding: EdgeInsets.symmetric(vertical: 20), padding: EdgeInsets.symmetric(vertical: 20),
child: Center( child: Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
); );
} }
else { else {
return LogTile( return LogTile(
log: logsData!.data[index], log: logsData!.data[index],
index: index, index: index,
length: logsData!.data.length, length: logsData!.data.length,
useAlwaysNormalTile: true, useAlwaysNormalTile: true,
onLogTap: (log) => { onLogTap: (log) => {
if (width > 700) { if (width > 700) {
showDialog( showDialog(
context: context, context: context,
builder: (context) => LogDetailsScreen(
log: log,
dialog: true
)
)
}
else {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LogDetailsScreen( builder: (context) => LogDetailsScreen(
log: log, log: log,
dialog: false dialog: true
) )
) )
) }
} else {
}, Navigator.of(context).push(
twoColumns: widget.splitView, MaterialPageRoute(
); builder: (context) => LogDetailsScreen(
log: log,
dialog: false
)
)
)
}
},
twoColumns: widget.splitView,
);
}
} }
}
),
);
}
else {
return Center(
child: Text(
AppLocalizations.of(context)!.noLogsDisplay,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
), );
); }
} else {
return Center(
case 2: child: Text(
return SizedBox( AppLocalizations.of(context)!.noLogsDisplay,
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.logsNotLoaded,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 22, fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
) ),
], );
), }
);
default: case 2:
return const SizedBox(); return SizedBox(
} width: double.maxFinite,
}, child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.logsNotLoaded,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
default:
return const SizedBox();
}
},
),
) )
); );
} }

View file

@ -11,7 +11,7 @@ import 'package:adguard_home_manager/providers/servers_provider.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart';
class Connect extends StatefulWidget { class Connect extends StatefulWidget {
const Connect({Key? key}) : super(key: key); const Connect({super.key});
@override @override
State<Connect> createState() => _ConnectState(); State<Connect> createState() => _ConnectState();
@ -61,26 +61,28 @@ class _ConnectState extends State<Connect> {
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context)!.connect), title: Text(AppLocalizations.of(context)!.connect),
), ),
body: Stack( body: SafeArea(
children: [ child: Stack(
ServersList( children: [
context: context, ServersList(
controllers: expandableControllerList, context: context,
onChange: expandOrContract, controllers: expandableControllerList,
scrollController: scrollController, onChange: expandOrContract,
breakingWidth: 700, scrollController: scrollController,
), breakingWidth: 700,
AnimatedPositioned( ),
duration: const Duration(milliseconds: 100), AnimatedPositioned(
curve: Curves.easeInOut, duration: const Duration(milliseconds: 100),
bottom: isVisible ? curve: Curves.easeInOut,
appConfigProvider.showingSnackbar bottom: isVisible ?
? 70 : 20 appConfigProvider.showingSnackbar
: -70, ? 90 : 20
right: 20, : -90,
child: const FabConnect() right: 20,
) child: const FabConnect()
], )
],
),
), ),
); );
} }

View file

@ -5,7 +5,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/models/filtering.dart'; import 'package:adguard_home_manager/models/filtering.dart';
class AddListModal extends StatefulWidget { class AddListModal extends StatelessWidget {
final String type; final String type;
final Filter? list; final Filter? list;
final void Function({required String name, required String url, required String type})? onConfirm; final void Function({required String name, required String url, required String type})? onConfirm;
@ -13,19 +13,74 @@ class AddListModal extends StatefulWidget {
final bool dialog; final bool dialog;
const AddListModal({ const AddListModal({
Key? key, super.key,
required this.type, required this.type,
this.list, this.list,
this.onConfirm, this.onConfirm,
this.onEdit, this.onEdit,
required this.dialog required this.dialog
}) : super(key: key); });
@override @override
State<AddListModal> createState() => _AddListModalState(); Widget build(BuildContext context) {
if (dialog == true) {
return Dialog(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 400
),
child: _Content(
list: list,
onConfirm: onConfirm,
onEdit: onEdit,
type: type,
)
),
);
}
else {
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(28),
topRight: Radius.circular(28)
),
color: Theme.of(context).dialogBackgroundColor
),
child: SafeArea(
child: _Content(
list: list,
onConfirm: onConfirm,
onEdit: onEdit,
type: type,
),
)
),
);
}
}
} }
class _AddListModalState extends State<AddListModal> { class _Content extends StatefulWidget {
final String type;
final Filter? list;
final void Function({required String name, required String url, required String type})? onConfirm;
final void Function({required Filter list, required String type})? onEdit;
const _Content({
required this.type,
required this.list,
required this.onConfirm,
required this.onEdit,
});
@override
State<_Content> createState() => _ContentState();
}
class _ContentState extends State<_Content> {
final TextEditingController nameController = TextEditingController(); final TextEditingController nameController = TextEditingController();
final TextEditingController urlController = TextEditingController(); final TextEditingController urlController = TextEditingController();
String? urlError; String? urlError;
@ -70,161 +125,133 @@ class _AddListModalState extends State<AddListModal> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget content() { return Column(
return Column( mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: [
children: [ Flexible(
Flexible( child: SingleChildScrollView(
child: SingleChildScrollView( child: Wrap(
child: Wrap(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 24),
child: Icon(
widget.type == 'whitelist'
? Icons.verified_user_rounded
: Icons.gpp_bad_rounded,
size: 24,
color: Theme.of(context).listTileTheme.iconColor
),
),
const SizedBox(height: 16),
Text(
widget.list != null
? widget.type == 'whitelist'
? AppLocalizations.of(context)!.editWhitelist
: AppLocalizations.of(context)!.editBlacklist
: widget.type == 'whitelist'
? AppLocalizations.of(context)!.addWhitelist
: AppLocalizations.of(context)!.addBlacklist,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
color: Theme.of(context).colorScheme.onSurface
),
),
const SizedBox(height: 16),
],
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextFormField(
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,
),
),
),
Container(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextFormField(
controller: urlController,
onChanged: validateUrl,
enabled: widget.list != null ? false : true,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: urlError,
labelText: AppLocalizations.of(context)!.urlAbsolutePath,
),
),
),
],
),
),
),
Padding(
padding: const EdgeInsets.all(24),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
TextButton( Row(
onPressed: () => Navigator.pop(context), mainAxisAlignment: MainAxisAlignment.center,
child: Text(AppLocalizations.of(context)!.cancel) children: [
Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 24),
child: Icon(
widget.type == 'whitelist'
? Icons.verified_user_rounded
: Icons.gpp_bad_rounded,
size: 24,
color: Theme.of(context).listTileTheme.iconColor
),
),
const SizedBox(height: 16),
Text(
widget.list != null
? widget.type == 'whitelist'
? AppLocalizations.of(context)!.editWhitelist
: AppLocalizations.of(context)!.editBlacklist
: widget.type == 'whitelist'
? AppLocalizations.of(context)!.addWhitelist
: AppLocalizations.of(context)!.addBlacklist,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
color: Theme.of(context).colorScheme.onSurface
),
),
const SizedBox(height: 16),
],
),
],
), ),
const SizedBox(width: 20), Padding(
TextButton( padding: const EdgeInsets.symmetric(horizontal: 24),
onPressed: () { child: TextFormField(
Navigator.pop(context); controller: nameController,
if (widget.list != null) { onChanged: (_) => checkValidValues(),
final Filter newList = Filter( decoration: InputDecoration(
url: urlController.text, prefixIcon: const Icon(Icons.badge_rounded),
name: nameController.text, border: const OutlineInputBorder(
lastUpdated: widget.list!.lastUpdated, borderRadius: BorderRadius.all(
id: widget.list!.id, Radius.circular(10)
rulesCount: widget.list!.rulesCount, )
enabled: widget.list!.enabled ),
); labelText: AppLocalizations.of(context)!.name,
widget.onEdit!( ),
list: newList, ),
type: widget.type ),
); Container(height: 30),
} Padding(
else { padding: const EdgeInsets.symmetric(horizontal: 24),
widget.onConfirm!( child: TextFormField(
name: nameController.text, controller: urlController,
url: urlController.text, onChanged: validateUrl,
type: widget.type enabled: widget.list != null ? false : true,
); decoration: InputDecoration(
} prefixIcon: const Icon(Icons.link_rounded),
}, border: const OutlineInputBorder(
child: Text( borderRadius: BorderRadius.all(
widget.list != null Radius.circular(10)
? AppLocalizations.of(context)!.save )
: AppLocalizations.of(context)!.confirm ),
) errorText: urlError,
labelText: AppLocalizations.of(context)!.urlAbsolutePath,
),
),
), ),
], ],
), ),
), ),
if (Platform.isIOS) const SizedBox(height: 16)
],
);
}
if (widget.dialog == true) {
return Dialog(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 400
),
child: content()
), ),
); Padding(
} padding: const EdgeInsets.all(24),
else { child: Row(
return Padding( mainAxisAlignment: MainAxisAlignment.end,
padding: MediaQuery.of(context).viewInsets, children: [
child: Container( TextButton(
decoration: BoxDecoration( onPressed: () => Navigator.pop(context),
borderRadius: const BorderRadius.only( child: Text(AppLocalizations.of(context)!.cancel)
topLeft: Radius.circular(28), ),
topRight: Radius.circular(28) const SizedBox(width: 20),
), TextButton(
color: Theme.of(context).dialogBackgroundColor onPressed: () {
Navigator.pop(context);
if (widget.list != null) {
final Filter newList = Filter(
url: urlController.text,
name: nameController.text,
lastUpdated: widget.list!.lastUpdated,
id: widget.list!.id,
rulesCount: widget.list!.rulesCount,
enabled: widget.list!.enabled
);
widget.onEdit!(
list: newList,
type: widget.type
);
}
else {
widget.onConfirm!(
name: nameController.text,
url: urlController.text,
type: widget.type
);
}
},
child: Text(
widget.list != null
? AppLocalizations.of(context)!.save
: AppLocalizations.of(context)!.confirm
)
),
],
), ),
child: content()
), ),
); if (Platform.isIOS) const SizedBox(height: 16)
} ],
);
} }
} }

View file

@ -8,19 +8,55 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart';
import 'package:adguard_home_manager/functions/get_filtered_status.dart'; import 'package:adguard_home_manager/functions/get_filtered_status.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart';
class CheckHostModal extends StatefulWidget { class CheckHostModal extends StatelessWidget {
final bool dialog; final bool dialog;
const CheckHostModal({ const CheckHostModal({
Key? key, super.key,
required this.dialog required this.dialog
}) : super(key: key); });
@override @override
State<CheckHostModal> createState() => _CheckHostModalState(); Widget build(BuildContext context) {
if (dialog == true) {
return Dialog(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 400
),
child: const _Content()
),
);
}
else {
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(28),
topRight: Radius.circular(28),
),
color: Theme.of(context).dialogBackgroundColor
),
child: const SafeArea(
child: _Content()
)
),
);
}
}
} }
class _CheckHostModalState extends State<CheckHostModal> { class _Content extends StatefulWidget {
const _Content();
@override
State<_Content> createState() => _ContentState();
}
class _ContentState extends State<_Content> {
final TextEditingController domainController = TextEditingController(); final TextEditingController domainController = TextEditingController();
String? domainError; String? domainError;
@ -59,17 +95,29 @@ class _CheckHostModalState extends State<CheckHostModal> {
setState(() => resultWidget = checking()); setState(() => resultWidget = checking());
final result = await serversProvider.apiClient2!.checkHostFiltered(host: domainController.text); final result = await serversProvider.apiClient2!.checkHostFiltered(host: domainController.text);
if (!mounted) return;
if (mounted) { if (result.successful == true) {
if (result.successful == true) { final status = getFilteredStatus(context, appConfigProvider, result.content['reason'], true);
final status = getFilteredStatus(context, appConfigProvider, result.content['reason'], true); if (mounted) {
if (mounted) { setState(() => resultWidget = Row(
setState(() => resultWidget = Row( mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, children: [
children: [ Icon(
Icon( status['icon'],
status['icon'], size: 18,
size: 18, color: status['filtered'] == true
? appConfigProvider.useThemeColorForStatus == true
? Colors.grey
: Colors.red
: appConfigProvider.useThemeColorForStatus
? Theme.of(context).colorScheme.primary
: Colors.green,
),
const SizedBox(width: 10),
Text(
status['label'],
style: TextStyle(
color: status['filtered'] == true color: status['filtered'] == true
? appConfigProvider.useThemeColorForStatus == true ? appConfigProvider.useThemeColorForStatus == true
? Colors.grey ? Colors.grey
@ -77,39 +125,6 @@ class _CheckHostModalState extends State<CheckHostModal> {
: appConfigProvider.useThemeColorForStatus : appConfigProvider.useThemeColorForStatus
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary
: Colors.green, : Colors.green,
),
const SizedBox(width: 10),
Text(
status['label'],
style: TextStyle(
color: status['filtered'] == true
? appConfigProvider.useThemeColorForStatus == true
? Colors.grey
: Colors.red
: appConfigProvider.useThemeColorForStatus
? Theme.of(context).colorScheme.primary
: Colors.green,
fontWeight: FontWeight.w500
),
)
],
));
}
}
else {
setState(() => resultWidget = Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.cancel,
size: 18,
color: Colors.red,
),
const SizedBox(width: 10),
Text(
AppLocalizations.of(context)!.check,
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.w500 fontWeight: FontWeight.w500
), ),
) )
@ -117,143 +132,134 @@ class _CheckHostModalState extends State<CheckHostModal> {
)); ));
} }
} }
else {
setState(() => resultWidget = Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.cancel,
size: 18,
color: Colors.red,
),
const SizedBox(width: 10),
Text(
AppLocalizations.of(context)!.check,
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.w500
),
)
],
));
}
} }
Widget content() { return Column(
return Column( mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: [
children: [ Flexible(
Flexible( child: SingleChildScrollView(
child: SingleChildScrollView( child: Wrap(
child: Wrap( children: [
children: [ Row(
Row( mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, children: [
children: [ Column(
Column( children: [
children: [ Padding(
Padding( padding: const EdgeInsets.only(top: 24),
padding: const EdgeInsets.only(top: 24), child: Icon(
child: Icon( Icons.shield_rounded,
Icons.shield_rounded, size: 24,
size: 24, color: Theme.of(context).listTileTheme.iconColor
color: Theme.of(context).listTileTheme.iconColor
),
), ),
const SizedBox(height: 16),
Text(
AppLocalizations.of(context)!.checkHostFiltered,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
color: Theme.of(context).colorScheme.onSurface
),
),
const SizedBox(height: 16),
],
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextFormField(
controller: domainController,
onChanged: validateDomain,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
), ),
errorText: domainError, const SizedBox(height: 16),
labelText: AppLocalizations.of(context)!.domain, Text(
AppLocalizations.of(context)!.checkHostFiltered,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
color: Theme.of(context).colorScheme.onSurface
),
),
const SizedBox(height: 16),
],
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextFormField(
controller: domainController,
onChanged: validateDomain,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: domainError,
labelText: AppLocalizations.of(context)!.domain,
),
),
),
if (resultWidget != null) Padding(
padding: const EdgeInsets.all(24),
child: resultWidget,
),
if (resultWidget == null) Padding(
padding: const EdgeInsets.all(24),
child: Center(
child: Text(
AppLocalizations.of(context)!.insertDomain,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 16,
), ),
), ),
), ),
if (resultWidget != null) Padding( ),
padding: const EdgeInsets.all(24), ],
child: resultWidget, ),
),
),
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.only(
bottom: 24,
right: 24
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.close),
), ),
if (resultWidget == null) Padding( const SizedBox(width: 20),
padding: const EdgeInsets.all(24), TextButton(
child: Center( onPressed: domainController.text != '' && domainError == null
child: Text( ? () => checkHost()
AppLocalizations.of(context)!.insertDomain, : null,
textAlign: TextAlign.center, child: Text(
style: const TextStyle( AppLocalizations.of(context)!.check,
fontSize: 16, style: TextStyle(
), color: domainController.text != '' && domainError == null
? Theme.of(context).colorScheme.primary
: Colors.grey
), ),
), ),
), ),
], ],
), ),
), )
), ],
Column( )
mainAxisAlignment: MainAxisAlignment.end, ],
children: [ );
Padding(
padding: const EdgeInsets.only(
bottom: 24,
right: 24
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.close),
),
const SizedBox(width: 20),
TextButton(
onPressed: domainController.text != '' && domainError == null
? () => checkHost()
: null,
child: Text(
AppLocalizations.of(context)!.check,
style: TextStyle(
color: domainController.text != '' && domainError == null
? Theme.of(context).colorScheme.primary
: Colors.grey
),
),
),
],
),
)
],
)
],
);
}
if (widget.dialog == true) {
return Dialog(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 400
),
child: content()
),
);
}
else {
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(28),
topRight: Radius.circular(28),
),
color: Theme.of(context).dialogBackgroundColor
),
child: content()
),
);
}
} }
} }

View file

@ -25,11 +25,11 @@ class ListDetailsScreen extends StatefulWidget {
final bool dialog; final bool dialog;
const ListDetailsScreen({ const ListDetailsScreen({
Key? key, super.key,
required this.listId, required this.listId,
required this.type, required this.type,
required this.dialog required this.dialog
}) : super(key: key); });
@override @override
State<ListDetailsScreen> createState() => _ListDetailsScreenState(); State<ListDetailsScreen> createState() => _ListDetailsScreenState();
@ -367,42 +367,44 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
title: Text(AppLocalizations.of(context)!.listDetails), title: Text(AppLocalizations.of(context)!.listDetails),
actions: list != null ? actions() : null, actions: list != null ? actions() : null,
), ),
body: Stack( body: SafeArea(
children: [ child: Stack(
if (list != null) ListView( children: [
children: content(), if (list != null) ListView(
), children: content(),
if (list == null) Center( ),
child: Text( if (list == null) Center(
AppLocalizations.of(context)!.listNotAvailable, child: Text(
style: const TextStyle( AppLocalizations.of(context)!.listNotAvailable,
fontSize: 24, style: const TextStyle(
fontSize: 24,
),
), ),
), ),
), if (list != null) AnimatedPositioned(
if (list != null) AnimatedPositioned( duration: const Duration(milliseconds: 100),
duration: const Duration(milliseconds: 100), curve: Curves.easeInOut,
curve: Curves.easeInOut, bottom: fabVisible ?
bottom: fabVisible ? appConfigProvider.showingSnackbar
appConfigProvider.showingSnackbar ? 70 : (Platform.isIOS ? 40 : 20)
? 70 : (Platform.isIOS ? 40 : 20) : -70,
: -70, right: 20,
right: 20, child: FloatingActionButton(
child: FloatingActionButton( onPressed: () => updateList(
onPressed: () => updateList( action: list!.enabled == true
action: list!.enabled == true ? FilteringListActions.disable
? FilteringListActions.disable : FilteringListActions.enable,
: FilteringListActions.enable, filterList: list
filterList: list ),
child: Icon(
list.enabled == true
? Icons.gpp_bad_rounded
: Icons.verified_user_rounded,
),
), ),
child: Icon( )
list.enabled == true ],
? Icons.gpp_bad_rounded ),
: Icons.verified_user_rounded,
),
),
)
],
), ),
), ),
); );

View file

@ -10,10 +10,10 @@ class AddCustomRule extends StatefulWidget {
final bool fullScreen; final bool fullScreen;
const AddCustomRule({ const AddCustomRule({
Key? key, super.key,
required this.onConfirm, required this.onConfirm,
required this.fullScreen required this.fullScreen
}) : super(key: key); });
@override @override
State<AddCustomRule> createState() => _AddCustomRuleState(); State<AddCustomRule> createState() => _AddCustomRuleState();
@ -349,8 +349,10 @@ class _AddCustomRuleState extends State<AddCustomRule> {
const SizedBox(width: 10) const SizedBox(width: 10)
], ],
), ),
body: ListView( body: SafeArea(
children: content(), child: ListView(
children: content(),
),
) )
), ),
); );

View file

@ -17,9 +17,9 @@ class BlockedServicesScreen extends StatefulWidget {
final bool fullScreen; final bool fullScreen;
const BlockedServicesScreen({ const BlockedServicesScreen({
Key? key, super.key,
required this.fullScreen required this.fullScreen
}) : super(key: key); });
@override @override
State<BlockedServicesScreen> createState() => _BlockedServicesScreenStateWidget(); State<BlockedServicesScreen> createState() => _BlockedServicesScreenStateWidget();
@ -83,105 +83,6 @@ class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreen> {
} }
} }
Widget body() {
switch (filteringProvider.blockedServicesLoadStatus) {
case LoadStatus.loading:
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.loadingBlockedServicesList,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
case LoadStatus.loaded:
return ListView.builder(
itemCount: filteringProvider.blockedServices!.services.length,
itemBuilder: (context, index) => Material(
color: Colors.transparent,
child: InkWell(
onTap: () => updateValues(
values.contains(filteringProvider.blockedServices!.services[index].id),
filteringProvider.blockedServices!.services[index]
),
child: Padding(
padding: const EdgeInsets.only(
top: 6,
bottom: 6,
right: 12,
left: 24
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
filteringProvider.blockedServices!.services[index].name,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface
),
),
Checkbox(
value: values.contains(filteringProvider.blockedServices!.services[index].id),
onChanged: (value) => updateValues(
value!,
filteringProvider.blockedServices!.services[index]
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)
),
)
],
),
),
),
)
);
case LoadStatus.error:
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.blockedServicesListNotLoaded,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
default:
return const SizedBox();
}
}
if (widget.fullScreen == true) { if (widget.fullScreen == true) {
return Dialog.fullscreen( return Dialog.fullscreen(
child: Scaffold( child: Scaffold(
@ -199,18 +100,23 @@ class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreen> {
const SizedBox(width: 10) const SizedBox(width: 10)
], ],
), ),
body: RefreshIndicator( body: SafeArea(
onRefresh: () async { child: RefreshIndicator(
final result = await filteringProvider.loadBlockedServices(); onRefresh: () async {
if (result == false) { final result = await filteringProvider.loadBlockedServices();
showSnacbkar( if (result == false) {
appConfigProvider: appConfigProvider, showSnacbkar(
label: AppLocalizations.of(context)!.blockedServicesListNotLoaded, appConfigProvider: appConfigProvider,
color: Colors.red label: AppLocalizations.of(context)!.blockedServicesListNotLoaded,
); color: Colors.red
} );
}, }
child: body() },
child: _Content(
values: values,
updateValues: updateValues,
)
),
), ),
), ),
); );
@ -256,7 +162,10 @@ class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreen> {
), ),
), ),
Expanded( Expanded(
child: body() child: _Content(
values: values,
updateValues: updateValues,
)
), ),
], ],
) )
@ -266,6 +175,118 @@ class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreen> {
} }
} }
class _Content extends StatelessWidget {
final List<String> values;
final void Function(bool value, BlockedService item) updateValues;
const _Content({
required this.values,
required this.updateValues,
});
@override
Widget build(BuildContext context) {
final filteringProvider = Provider.of<FilteringProvider>(context);
switch (filteringProvider.blockedServicesLoadStatus) {
case LoadStatus.loading:
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.loadingBlockedServicesList,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
case LoadStatus.loaded:
return ListView.builder(
itemCount: filteringProvider.blockedServices!.services.length,
itemBuilder: (context, index) => Material(
color: Colors.transparent,
child: InkWell(
onTap: () => updateValues(
values.contains(filteringProvider.blockedServices!.services[index].id),
filteringProvider.blockedServices!.services[index]
),
child: Padding(
padding: const EdgeInsets.only(
top: 6,
bottom: 6,
right: 12,
left: 24
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
filteringProvider.blockedServices!.services[index].name,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface
),
),
Checkbox(
value: values.contains(filteringProvider.blockedServices!.services[index].id),
onChanged: (value) => updateValues(
value!,
filteringProvider.blockedServices!.services[index]
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)
),
)
],
),
),
),
)
);
case LoadStatus.error:
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.blockedServicesListNotLoaded,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
default:
return const SizedBox();
}
}
}
void openBlockedServicesModal({ void openBlockedServicesModal({
required BuildContext context, required BuildContext context,
required double width, required double width,

View file

@ -5,9 +5,9 @@ class DeleteListModal extends StatelessWidget {
final void Function() onConfirm; final void Function() onConfirm;
const DeleteListModal({ const DeleteListModal({
Key? key, super.key,
required this.onConfirm required this.onConfirm
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View file

@ -66,10 +66,12 @@ class _UpdateIntervalListsModalState extends State<UpdateIntervalListsModal> {
topRight: Radius.circular(28) topRight: Radius.circular(28)
), ),
), ),
child: _Content( child: SafeArea(
selectedOption: selectedOption, child: _Content(
onUpdateValue: _updateRadioValue, selectedOption: selectedOption,
onConfirm: () => widget.onChange(selectedOption!), onUpdateValue: _updateRadioValue,
onConfirm: () => widget.onChange(selectedOption!),
),
) )
), ),
); );

View file

@ -72,19 +72,18 @@ class SelectionSliverList extends StatelessWidget {
final void Function() unselectAll; final void Function() unselectAll;
const SelectionSliverList({ const SelectionSliverList({
Key? key, super.key,
required this.lists, required this.lists,
required this.selectedLists, required this.selectedLists,
required this.onSelect, required this.onSelect,
required this.selectAll, required this.selectAll,
required this.unselectAll, required this.unselectAll,
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return SafeArea(
top: false, top: false,
bottom: false,
child: Builder( child: Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
return CustomScrollView( return CustomScrollView(

View file

@ -19,9 +19,9 @@ class ManagementModal extends StatefulWidget {
final bool dialog; final bool dialog;
const ManagementModal({ const ManagementModal({
Key? key, super.key,
required this.dialog required this.dialog
}) : super(key: key); });
@override @override
State<ManagementModal> createState() => _ManagementModalState(); State<ManagementModal> createState() => _ManagementModalState();
@ -141,33 +141,35 @@ class _ManagementModalState extends State<ManagementModal> with SingleTickerProv
topRight: Radius.circular(28) topRight: Radius.circular(28)
) )
), ),
child: Column( child: SafeArea(
mainAxisSize: MainAxisSize.min, child: Column(
children: [ mainAxisSize: MainAxisSize.min,
Flexible( children: [
child: SingleChildScrollView( Flexible(
child: _Modal( child: SingleChildScrollView(
expandableController: expandableController, child: _Modal(
updateBlocking: updateBlocking, expandableController: expandableController,
disableWithCountdown: disableWithCountdown, updateBlocking: updateBlocking,
animation: animation, disableWithCountdown: disableWithCountdown,
) animation: animation,
)
),
), ),
), Padding(
Padding( padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(24), child: Row(
child: Row( mainAxisAlignment: MainAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end, children: [
children: [ TextButton(
TextButton( onPressed: () => Navigator.pop(context),
onPressed: () => Navigator.pop(context), child: Text(AppLocalizations.of(context)!.close),
child: Text(AppLocalizations.of(context)!.close), ),
), ],
], ),
), ),
), if (Platform.isIOS) const SizedBox(height: 16)
if (Platform.isIOS) const SizedBox(height: 16) ],
], ),
), ),
); );
} }
@ -201,24 +203,24 @@ class _Modal extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(top: 24), padding: const EdgeInsets.only(top: 24),
child: Icon( child: Icon(
Icons.shield_rounded, Icons.shield_rounded,
size: 24, size: 24,
color: Theme.of(context).listTileTheme.iconColor color: Theme.of(context).listTileTheme.iconColor
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
child: Text( child: Text(
AppLocalizations.of(context)!.manageServer, AppLocalizations.of(context)!.manageServer,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
color: Theme.of(context).colorScheme.onSurface, color: Theme.of(context).colorScheme.onSurface,
),
), ),
), ),
),
], ],
), ),
], ],

View file

@ -122,14 +122,16 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
const SizedBox(width: 8) const SizedBox(width: 8)
], ],
), ),
body: _Content( body: SafeArea(
buildValue: widget.buildValue, child: _Content(
isClient: widget.isClient, buildValue: widget.buildValue,
onTapEntry: widget.onTapEntry, isClient: widget.isClient,
options: widget.options, onTapEntry: widget.onTapEntry,
screenData: screenData, options: widget.options,
total: total, screenData: screenData,
withProgressBar: widget.withProgressBar, total: total,
withProgressBar: widget.withProgressBar,
),
), ),
), ),
); );

View file

@ -148,36 +148,38 @@ class _LogsConfigModalState extends State<LogsConfigModal> {
), ),
color: Theme.of(context).dialogBackgroundColor color: Theme.of(context).dialogBackgroundColor
), ),
child: Builder( child: SafeArea(
builder: (context) { child: Builder(
switch (loadStatus) { builder: (context) {
case LoadStatus.loading: switch (loadStatus) {
return const ConfigLogsLoading(); case LoadStatus.loading:
return const ConfigLogsLoading();
case LoadStatus.loaded: case LoadStatus.loaded:
return LogsConfigOptions( return LogsConfigOptions(
generalSwitch: generalSwitch, generalSwitch: generalSwitch,
updateGeneralSwitch: (v) => setState(() => generalSwitch = v), updateGeneralSwitch: (v) => setState(() => generalSwitch = v),
anonymizeClientIp: anonymizeClientIp, anonymizeClientIp: anonymizeClientIp,
updateAnonymizeClientIp: (v) => setState(() => anonymizeClientIp = v), updateAnonymizeClientIp: (v) => setState(() => anonymizeClientIp = v),
retentionItems: retentionItems, retentionItems: retentionItems,
retentionTime: retentionTime, retentionTime: retentionTime,
updateRetentionTime: (v) => setState(() => retentionTime = v), updateRetentionTime: (v) => setState(() => retentionTime = v),
onClear: () => widget.onClear(), onClear: () => widget.onClear(),
onConfirm: () => widget.onConfirm({ onConfirm: () => widget.onConfirm({
"enabled": generalSwitch, "enabled": generalSwitch,
"interval": retentionTime, "interval": retentionTime,
"anonymize_client_ip": anonymizeClientIp "anonymize_client_ip": anonymizeClientIp
}) })
); );
case LoadStatus.error: case LoadStatus.error:
return const ConfigLogsError(); return const ConfigLogsError();
default: default:
return const SizedBox(); return const SizedBox();
} }
}, },
),
) )
); );
} }

View file

@ -25,10 +25,10 @@ class LogDetailsScreen extends StatelessWidget {
final bool dialog; final bool dialog;
const LogDetailsScreen({ const LogDetailsScreen({
Key? key, super.key,
required this.log, required this.log,
required this.dialog required this.dialog
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -322,7 +322,6 @@ class LogDetailsScreen extends StatelessWidget {
], ],
body: SafeArea( body: SafeArea(
top: false, top: false,
bottom: false,
child: Builder( child: Builder(
builder: (context) => CustomScrollView( builder: (context) => CustomScrollView(
slivers: [ slivers: [

View file

@ -60,9 +60,11 @@ class _ClientsModalState extends State<ClientsModal> {
), ),
color: Theme.of(context).dialogBackgroundColor color: Theme.of(context).dialogBackgroundColor
), ),
child: _ModalContent( child: SafeArea(
selectedClients: selectedClients, child: _ModalContent(
onClientsSelected: (v) => setState(() => selectedClients = v), selectedClients: selectedClients,
onClientsSelected: (v) => setState(() => selectedClients = v),
),
) )
), ),
); );

View file

@ -62,10 +62,12 @@ class _FilterStatusModalState extends State<FilterStatusModal> {
), ),
color: Theme.of(context).dialogBackgroundColor color: Theme.of(context).dialogBackgroundColor
), ),
child: _Content( child: SafeArea(
onApply: apply, child: _Content(
updateSelectedResultStatus: (v) => setState(() => selectedResultStatus = v), onApply: apply,
selectedResultStatus: selectedResultStatus, updateSelectedResultStatus: (v) => setState(() => selectedResultStatus = v),
selectedResultStatus: selectedResultStatus,
),
) )
); );
} }

View file

@ -19,9 +19,9 @@ class LogsFiltersModal extends StatefulWidget {
final bool dialog; final bool dialog;
const LogsFiltersModal({ const LogsFiltersModal({
Key? key, super.key,
required this.dialog required this.dialog
}) : super(key: key); });
@override @override
State<LogsFiltersModal> createState() => _LogsFiltersModalState(); State<LogsFiltersModal> createState() => _LogsFiltersModalState();
@ -65,9 +65,11 @@ class _LogsFiltersModalState extends State<LogsFiltersModal> {
topRight: Radius.circular(28) topRight: Radius.circular(28)
) )
), ),
child: _FiltersList( child: SafeArea(
searchController: searchController, child: _FiltersList(
onClearSearch: () => setState(() => searchController.text = "") searchController: searchController,
onClearSearch: () => setState(() => searchController.text = "")
),
) )
), ),
); );

View file

@ -16,9 +16,9 @@ class Servers extends StatefulWidget {
final double? breakingWidth; final double? breakingWidth;
const Servers({ const Servers({
Key? key, super.key,
this.breakingWidth this.breakingWidth
}) : super(key: key); });
@override @override
State<Servers> createState() => _ServersState(); State<Servers> createState() => _ServersState();
@ -77,29 +77,31 @@ class _ServersState extends State<Servers> {
title: Text(AppLocalizations.of(context)!.servers), title: Text(AppLocalizations.of(context)!.servers),
centerTitle: false, centerTitle: false,
), ),
body: Stack( body: SafeArea(
children: [ child: Stack(
ServersList( children: [
context: context, ServersList(
controllers: expandableControllerList, context: context,
onChange: expandOrContract, controllers: expandableControllerList,
scrollController: scrollController, onChange: expandOrContract,
breakingWidth: widget.breakingWidth ?? 700, scrollController: scrollController,
), breakingWidth: widget.breakingWidth ?? 700,
AnimatedPositioned(
duration: const Duration(milliseconds: 100),
curve: Curves.easeInOut,
bottom: isVisible ?
appConfigProvider.showingSnackbar
? 70 : (Platform.isIOS ? 40 : 20)
: -70,
right: 20,
child: FloatingActionButton(
onPressed: openAddServerModal,
child: const Icon(Icons.add),
), ),
), AnimatedPositioned(
], duration: const Duration(milliseconds: 100),
curve: Curves.easeInOut,
bottom: isVisible ?
appConfigProvider.showingSnackbar
? 70 : (Platform.isIOS ? 40 : 20)
: -70,
right: 20,
child: FloatingActionButton(
onPressed: openAddServerModal,
child: const Icon(Icons.add),
),
),
],
),
), ),
); );
} }

View file

@ -44,9 +44,11 @@ class AddClientModal extends StatelessWidget {
topRight: Radius.circular(28) topRight: Radius.circular(28)
) )
), ),
child: _Content( child: SafeArea(
type: type, child: _Content(
onConfirm: onConfirm, type: type,
onConfirm: onConfirm,
),
) )
), ),
); );

View file

@ -11,7 +11,7 @@ import 'package:adguard_home_manager/functions/snackbar.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart';
class AdvancedSettings extends StatelessWidget { class AdvancedSettings extends StatelessWidget {
const AdvancedSettings({Key? key}) : super(key: key); const AdvancedSettings({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -46,31 +46,33 @@ class AdvancedSettings extends StatelessWidget {
title: Text(AppLocalizations.of(context)!.advancedSettings), title: Text(AppLocalizations.of(context)!.advancedSettings),
surfaceTintColor: isDesktop(width) ? Colors.transparent : null, surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
), ),
body: ListView( body: SafeArea(
children: [ child: ListView(
CustomListTile( children: [
icon: Icons.lock, CustomListTile(
title: AppLocalizations.of(context)!.dontCheckCertificate, icon: Icons.lock,
subtitle: AppLocalizations.of(context)!.dontCheckCertificateDescription, title: AppLocalizations.of(context)!.dontCheckCertificate,
trailing: Switch( subtitle: AppLocalizations.of(context)!.dontCheckCertificateDescription,
value: appConfigProvider.overrideSslCheck, trailing: Switch(
onChanged: (value) => updateSettings( value: appConfigProvider.overrideSslCheck,
newStatus: value, onChanged: (value) => updateSettings(
newStatus: value,
function: appConfigProvider.setOverrideSslCheck
),
),
onTap: () => updateSettings(
newStatus: !appConfigProvider.overrideSslCheck,
function: appConfigProvider.setOverrideSslCheck function: appConfigProvider.setOverrideSslCheck
), ),
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
left: 20,
right: 10
)
), ),
onTap: () => updateSettings( ],
newStatus: !appConfigProvider.overrideSslCheck, ),
function: appConfigProvider.setOverrideSslCheck
),
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
left: 20,
right: 10
)
),
],
) )
); );
} }

View file

@ -14,7 +14,7 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart';
import 'package:adguard_home_manager/constants/colors.dart'; import 'package:adguard_home_manager/constants/colors.dart';
class Customization extends StatelessWidget { class Customization extends StatelessWidget {
const Customization({Key? key}) : super(key: key); const Customization({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -65,152 +65,154 @@ class _CustomizationWidgetState extends State<CustomizationWidget> {
centerTitle: false, centerTitle: false,
surfaceTintColor: isDesktop(width) ? Colors.transparent : null, surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
), ),
body: ListView( body: SafeArea(
children: [ child: ListView(
SectionLabel( children: [
label: AppLocalizations.of(context)!.theme, SectionLabel(
padding: const EdgeInsets.only(top: 10, left: 16, right: 16, bottom: 5), label: AppLocalizations.of(context)!.theme,
), padding: const EdgeInsets.only(top: 10, left: 16, right: 16, bottom: 5),
Column(
children: [
CustomSwitchListTile(
value: selectedTheme == 0 ? true : false,
onChanged: (value) {
selectedTheme = value == true ? 0 : 1;
appConfigProvider.setSelectedTheme(value == true ? 0 : 1);
},
title: AppLocalizations.of(context)!.systemDefined,
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ThemeModeButton(
icon: Icons.light_mode,
value: 1,
selected: selectedTheme,
label: AppLocalizations.of(context)!.light,
onChanged: (value) {
selectedTheme = value;
appConfigProvider.setSelectedTheme(value);
},
disabled: selectedTheme == 0 ? true : false,
),
ThemeModeButton(
icon: Icons.dark_mode,
value: 2,
selected: selectedTheme,
label: AppLocalizations.of(context)!.dark,
onChanged: (value) {
selectedTheme = value;
appConfigProvider.setSelectedTheme(value);
},
disabled: selectedTheme == 0 ? true : false,
),
],
),
],
),
SectionLabel(
label: AppLocalizations.of(context)!.color,
padding: const EdgeInsets.only(top: 45, left: 16, right: 16, bottom: 5),
),
if (appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31) CustomSwitchListTile(
value: dynamicColor,
onChanged: (value) {
setState(() => dynamicColor = value);
appConfigProvider.setUseDynamicColor(value);
},
title: AppLocalizations.of(context)!.useDynamicTheme,
),
if (!(appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31)) const SizedBox(height: 20),
if (dynamicColor == false) ...[
SizedBox(
width: MediaQuery.of(context).size.width,
height: 70,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: colors.length,
itemBuilder: (context, index) {
if (index == 0) {
return Row(
children: [
const SizedBox(width: 15),
ColorItem(
color: colors[index],
numericValue: index,
selectedValue: selectedColor,
onChanged: (value) {
setState(() => selectedColor = value);
appConfigProvider.setStaticColor(value);
}
),
Container(
margin: const EdgeInsets.symmetric(horizontal: 10),
width: 1,
height: 60,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(1)
),
)
],
);
}
else if (index == colors.length-1) {
return Row(
children: [
ColorItem(
color: colors[index],
numericValue: index,
selectedValue: selectedColor,
onChanged: (value) {
setState(() => selectedColor = value);
appConfigProvider.setStaticColor(value);
}
),
const SizedBox(width: 15)
],
);
}
else {
return ColorItem(
color: colors[index],
numericValue: index,
selectedValue: selectedColor,
onChanged: (value) {
setState(() => selectedColor = value);
appConfigProvider.setStaticColor(value);
}
);
}
},
),
), ),
Padding( Column(
padding: const EdgeInsets.only( children: [
left: 25, CustomSwitchListTile(
top: 10 value: selectedTheme == 0 ? true : false,
), onChanged: (value) {
child: Text( selectedTheme = value == true ? 0 : 1;
colorTranslation(context, selectedColor), appConfigProvider.setSelectedTheme(value == true ? 0 : 1);
style: TextStyle( },
color: Theme.of(context).listTileTheme.iconColor, title: AppLocalizations.of(context)!.systemDefined,
fontSize: 16 ),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ThemeModeButton(
icon: Icons.light_mode,
value: 1,
selected: selectedTheme,
label: AppLocalizations.of(context)!.light,
onChanged: (value) {
selectedTheme = value;
appConfigProvider.setSelectedTheme(value);
},
disabled: selectedTheme == 0 ? true : false,
),
ThemeModeButton(
icon: Icons.dark_mode,
value: 2,
selected: selectedTheme,
label: AppLocalizations.of(context)!.dark,
onChanged: (value) {
selectedTheme = value;
appConfigProvider.setSelectedTheme(value);
},
disabled: selectedTheme == 0 ? true : false,
),
],
),
],
),
SectionLabel(
label: AppLocalizations.of(context)!.color,
padding: const EdgeInsets.only(top: 45, left: 16, right: 16, bottom: 5),
),
if (appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31) CustomSwitchListTile(
value: dynamicColor,
onChanged: (value) {
setState(() => dynamicColor = value);
appConfigProvider.setUseDynamicColor(value);
},
title: AppLocalizations.of(context)!.useDynamicTheme,
),
if (!(appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31)) const SizedBox(height: 20),
if (dynamicColor == false) ...[
SizedBox(
width: MediaQuery.of(context).size.width,
height: 70,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: colors.length,
itemBuilder: (context, index) {
if (index == 0) {
return Row(
children: [
const SizedBox(width: 15),
ColorItem(
color: colors[index],
numericValue: index,
selectedValue: selectedColor,
onChanged: (value) {
setState(() => selectedColor = value);
appConfigProvider.setStaticColor(value);
}
),
Container(
margin: const EdgeInsets.symmetric(horizontal: 10),
width: 1,
height: 60,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(1)
),
)
],
);
}
else if (index == colors.length-1) {
return Row(
children: [
ColorItem(
color: colors[index],
numericValue: index,
selectedValue: selectedColor,
onChanged: (value) {
setState(() => selectedColor = value);
appConfigProvider.setStaticColor(value);
}
),
const SizedBox(width: 15)
],
);
}
else {
return ColorItem(
color: colors[index],
numericValue: index,
selectedValue: selectedColor,
onChanged: (value) {
setState(() => selectedColor = value);
appConfigProvider.setStaticColor(value);
}
);
}
},
), ),
), ),
Padding(
padding: const EdgeInsets.only(
left: 25,
top: 10
),
child: Text(
colorTranslation(context, selectedColor),
style: TextStyle(
color: Theme.of(context).listTileTheme.iconColor,
fontSize: 16
),
),
)
],
CustomSwitchListTile(
value: useThemeColorInsteadGreenRed,
onChanged: (value) {
setState(() => useThemeColorInsteadGreenRed = value);
appConfigProvider.setUseThemeColorForStatus(value);
},
title: AppLocalizations.of(context)!.useThemeColorStatus,
subtitle: AppLocalizations.of(context)!.useThemeColorStatusDescription,
) )
], ],
CustomSwitchListTile( ),
value: useThemeColorInsteadGreenRed,
onChanged: (value) {
setState(() => useThemeColorInsteadGreenRed = value);
appConfigProvider.setUseThemeColorForStatus(value);
},
title: AppLocalizations.of(context)!.useThemeColorStatus,
subtitle: AppLocalizations.of(context)!.useThemeColorStatusDescription,
)
],
), ),
); );
} }

View file

@ -3,7 +3,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/models/dhcp.dart'; import 'package:adguard_home_manager/models/dhcp.dart';
class AddStaticLeaseModal extends StatefulWidget { class AddStaticLeaseModal extends StatelessWidget {
final void Function(Lease) onConfirm; final void Function(Lease) onConfirm;
final bool dialog; final bool dialog;
@ -14,10 +14,49 @@ class AddStaticLeaseModal extends StatefulWidget {
}); });
@override @override
State<AddStaticLeaseModal> createState() => _AddStaticLeaseModalState(); Widget build(BuildContext context) {
if (dialog == true) {
return Dialog(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 400
),
child: _Content(onConfirm: onConfirm)
),
);
}
else {
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).dialogBackgroundColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(28),
topRight: Radius.circular(28)
)
),
child: SafeArea(
child: _Content(onConfirm: onConfirm)
)
),
);
}
}
} }
class _AddStaticLeaseModalState extends State<AddStaticLeaseModal> { class _Content extends StatefulWidget {
final void Function(Lease) onConfirm;
const _Content({
required this.onConfirm
});
@override
State<_Content> createState() => __ContentState();
}
class __ContentState extends State<_Content> {
final TextEditingController macController = TextEditingController(); final TextEditingController macController = TextEditingController();
String? macError; String? macError;
final TextEditingController ipController = TextEditingController(); final TextEditingController ipController = TextEditingController();
@ -67,175 +106,147 @@ class _AddStaticLeaseModalState extends State<AddStaticLeaseModal> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget content() { return Column(
return Column( mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: [
children: [ Flexible(
Flexible( child: SingleChildScrollView(
child: SingleChildScrollView( child: Wrap(
child: Wrap(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 24),
child: Icon(
Icons.add,
size: 24,
color: Theme.of(context).listTileTheme.iconColor
),
),
const SizedBox(height: 16),
Text(
AppLocalizations.of(context)!.addStaticLease,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
color: Theme.of(context).colorScheme.onSurface
),
),
],
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(
left: 24, right: 24, bottom: 12
),
child: TextFormField(
controller: macController,
onChanged: validateMac,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.smartphone_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: macError,
labelText: AppLocalizations.of(context)!.macAddress,
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
child: TextFormField(
controller: ipController,
onChanged: validateIp,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipError,
labelText: AppLocalizations.of(context)!.ipAddress,
),
),
),
Padding(
padding: const EdgeInsets.only(
left: 24, right: 24, top: 12
),
child: TextFormField(
controller: hostNameController,
onChanged: (value) {
if (value != '') {
setState(() => hostNameError = null);
}
else {
setState(() => hostNameError = AppLocalizations.of(context)!.hostNameError);
}
validateData();
},
decoration: InputDecoration(
prefixIcon: const Icon(Icons.badge_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: hostNameError,
labelText: AppLocalizations.of(context)!.hostName,
),
),
),
],
),
),
),
Padding(
padding: const EdgeInsets.all(24),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
TextButton( Padding(
onPressed: () => Navigator.pop(context), padding: const EdgeInsets.only(bottom: 16),
child: Text(AppLocalizations.of(context)!.cancel), child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 24),
child: Icon(
Icons.add,
size: 24,
color: Theme.of(context).listTileTheme.iconColor
),
),
const SizedBox(height: 16),
Text(
AppLocalizations.of(context)!.addStaticLease,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
color: Theme.of(context).colorScheme.onSurface
),
),
],
),
),
],
),
), ),
const SizedBox(width: 20), Padding(
TextButton( padding: const EdgeInsets.only(
onPressed: validData == true left: 24, right: 24, bottom: 12
? () { ),
Navigator.pop(context); child: TextFormField(
widget.onConfirm( controller: macController,
Lease( onChanged: validateMac,
mac: macController.text, decoration: InputDecoration(
hostname: hostNameController.text, prefixIcon: const Icon(Icons.smartphone_rounded),
ip: ipController.text border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
) )
); ),
} errorText: macError,
: null, labelText: AppLocalizations.of(context)!.macAddress,
child: Text( ),
AppLocalizations.of(context)!.confirm, ),
style: TextStyle( ),
color: validData == true Padding(
? Theme.of(context).colorScheme.primary padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
: Colors.grey child: TextFormField(
controller: ipController,
onChanged: validateIp,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipError,
labelText: AppLocalizations.of(context)!.ipAddress,
),
),
),
Padding(
padding: const EdgeInsets.only(
left: 24, right: 24, top: 12
),
child: TextFormField(
controller: hostNameController,
onChanged: (value) {
if (value != '') {
setState(() => hostNameError = null);
}
else {
setState(() => hostNameError = AppLocalizations.of(context)!.hostNameError);
}
validateData();
},
decoration: InputDecoration(
prefixIcon: const Icon(Icons.badge_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: hostNameError,
labelText: AppLocalizations.of(context)!.hostName,
), ),
), ),
), ),
], ],
), ),
)
],
);
}
if (widget.dialog == true) {
return Dialog(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 400
), ),
child: content(),
), ),
); Padding(
} padding: const EdgeInsets.all(24),
else { child: Row(
return Padding( mainAxisAlignment: MainAxisAlignment.end,
padding: MediaQuery.of(context).viewInsets, children: [
child: Container( TextButton(
decoration: BoxDecoration( onPressed: () => Navigator.pop(context),
color: Theme.of(context).dialogBackgroundColor, child: Text(AppLocalizations.of(context)!.cancel),
borderRadius: const BorderRadius.only( ),
topLeft: Radius.circular(28), const SizedBox(width: 20),
topRight: Radius.circular(28) TextButton(
) onPressed: validData == true
? () {
Navigator.pop(context);
widget.onConfirm(
Lease(
mac: macController.text,
hostname: hostNameController.text,
ip: ipController.text
)
);
}
: null,
child: Text(
AppLocalizations.of(context)!.confirm,
style: TextStyle(
color: validData == true
? Theme.of(context).colorScheme.primary
: Colors.grey
),
),
),
],
), ),
child: content() )
), ],
); );
}
} }
} }

View file

@ -411,328 +411,330 @@ class _DhcpScreenState extends State<DhcpScreen> {
const SizedBox(width: 10) const SizedBox(width: 10)
] : null, ] : null,
), ),
body: Builder( body: SafeArea(
builder: (context) { child: Builder(
switch (dhcpProvider.loadStatus) { builder: (context) {
case LoadStatus.loading: switch (dhcpProvider.loadStatus) {
return SizedBox( case LoadStatus.loading:
width: double.maxFinite, return SizedBox(
child: Column( width: double.maxFinite,
mainAxisAlignment: MainAxisAlignment.center, child: Column(
crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ crossAxisAlignment: CrossAxisAlignment.center,
const CircularProgressIndicator(),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.loadingDhcp,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
case LoadStatus.loaded:
if (selectedInterface != null) {
return SingleChildScrollView(
child: Wrap(
children: [ children: [
DhcpMainButton( const CircularProgressIndicator(),
selectedInterface: selectedInterface, const SizedBox(height: 30),
enabled: enabled, Text(
setEnabled: (v) => setState(() => enabled = v) AppLocalizations.of(context)!.loadingDhcp,
), textAlign: TextAlign.center,
if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[ style: TextStyle(
SectionLabel( fontSize: 22,
label: AppLocalizations.of(context)!.ipv4settings, color: Theme.of(context).colorScheme.onSurfaceVariant,
padding: const EdgeInsets.only(
top: 24, left: 16, right: 16, bottom: 8
)
), ),
_DhcpField( )
icon: Icons.skip_previous_rounded,
label: AppLocalizations.of(context)!.startOfRange,
controller: ipv4StartRangeController,
onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid),
error: ipv4StartRangeError
),
_DhcpField(
icon: Icons.skip_next_rounded,
label: AppLocalizations.of(context)!.endOfRange,
controller: ipv4EndRangeController,
onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid),
error: ipv4EndRangeError
),
_DhcpField(
icon: Icons.hub_rounded,
label: AppLocalizations.of(context)!.subnetMask,
controller: ipv4SubnetMaskController,
onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid),
error: ipv4SubnetMaskError
),
_DhcpField(
icon: Icons.router_rounded,
label: AppLocalizations.of(context)!.gateway,
controller: ipv4GatewayController,
onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid),
error: ipv4GatewayError
),
_DhcpField(
icon: Icons.timer,
label: AppLocalizations.of(context)!.leaseTime,
controller: ipv4LeaseTimeController,
onChanged: (value) {
if (int.tryParse(value).runtimeType == int) {
setState(() => ipv4LeaseTimeError = null);
}
else {
setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid);
}
},
error: ipv4LeaseTimeError
),
],
if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[
SectionLabel(
label: AppLocalizations.of(context)!.ipv6settings,
padding: const EdgeInsets.all(16)
),
_DhcpField(
icon: Icons.skip_next_rounded,
label: AppLocalizations.of(context)!.startOfRange,
controller: ipv6StartRangeController,
onChanged: (value) => validateIpV6(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid),
error: ipv6StartRangeError
),
_DhcpField(
icon: Icons.skip_previous_rounded,
label: AppLocalizations.of(context)!.endOfRange,
controller: ipv6EndRangeController,
onChanged: (value) => validateIpV6(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid),
error: ipv6EndRangeError
),
_DhcpField(
icon: Icons.timer,
label: AppLocalizations.of(context)!.leaseTime,
controller: ipv6LeaseTimeController,
onChanged: (value) {
if (int.tryParse(value).runtimeType == int) {
setState(() => ipv6LeaseTimeError = null);
}
else {
setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid);
}
},
error: ipv6LeaseTimeError
)
],
const SizedBox(height: 20),
SectionLabel(
label: AppLocalizations.of(context)!.dhcpLeases,
padding: const EdgeInsets.all(16),
),
if (width <= 900) Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus!.leases,
staticLeases: false,
)
)
);
},
child: Container(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
AppLocalizations.of(context)!.dhcpLeases,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface,
),
),
Icon(
Icons.arrow_forward_rounded,
color: Theme.of(context).colorScheme.onSurface,
)
],
),
),
),
),
if (width <= 900) Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases,
staticLeases: true,
)
)
);
},
child: Container(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
AppLocalizations.of(context)!.dhcpStatic,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface,
),
),
Icon(
Icons.arrow_forward_rounded,
color: Theme.of(context).colorScheme.onSurface,
)
],
),
),
),
),
if (width > 900) Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
if (!(Platform.isAndroid || Platform.isIOS)) {
SplitView.of(context).push(
DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus!.leases,
staticLeases: false,
)
);
}
else {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus!.leases,
staticLeases: false,
)
)
);
}
},
child: Row(
children: [
Text(AppLocalizations.of(context)!.dhcpLeases),
const SizedBox(width: 8),
const Icon(Icons.arrow_forward_rounded)
],
)
),
ElevatedButton(
onPressed: () {
if (!(Platform.isAndroid || Platform.isIOS)) {
SplitView.of(context).push(
DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases,
staticLeases: true,
)
);
}
else {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases,
staticLeases: true,
)
)
);
}
},
child: Row(
children: [
Text(AppLocalizations.of(context)!.dhcpStatic),
const SizedBox(width: 8),
const Icon(Icons.arrow_forward_rounded)
],
)
),
],
),
const SizedBox(height: 10)
], ],
), ),
); );
}
else { case LoadStatus.loaded:
return Row( if (selectedInterface != null) {
mainAxisAlignment: MainAxisAlignment.center, return SingleChildScrollView(
children: [ child: Wrap(
Flexible( children: [
child: Column( DhcpMainButton(
mainAxisSize: MainAxisSize.max, selectedInterface: selectedInterface,
mainAxisAlignment: MainAxisAlignment.center, enabled: enabled,
crossAxisAlignment: CrossAxisAlignment.center, setEnabled: (v) => setState(() => enabled = v)
children: [ ),
Padding( if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[
padding: const EdgeInsets.symmetric(horizontal: 20), SectionLabel(
child: Text( label: AppLocalizations.of(context)!.ipv4settings,
AppLocalizations.of(context)!.neededSelectInterface, padding: const EdgeInsets.only(
textAlign: TextAlign.center, top: 24, left: 16, right: 16, bottom: 8
style: TextStyle( )
fontSize: 22, ),
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5) _DhcpField(
icon: Icons.skip_previous_rounded,
label: AppLocalizations.of(context)!.startOfRange,
controller: ipv4StartRangeController,
onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid),
error: ipv4StartRangeError
),
_DhcpField(
icon: Icons.skip_next_rounded,
label: AppLocalizations.of(context)!.endOfRange,
controller: ipv4EndRangeController,
onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid),
error: ipv4EndRangeError
),
_DhcpField(
icon: Icons.hub_rounded,
label: AppLocalizations.of(context)!.subnetMask,
controller: ipv4SubnetMaskController,
onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid),
error: ipv4SubnetMaskError
),
_DhcpField(
icon: Icons.router_rounded,
label: AppLocalizations.of(context)!.gateway,
controller: ipv4GatewayController,
onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid),
error: ipv4GatewayError
),
_DhcpField(
icon: Icons.timer,
label: AppLocalizations.of(context)!.leaseTime,
controller: ipv4LeaseTimeController,
onChanged: (value) {
if (int.tryParse(value).runtimeType == int) {
setState(() => ipv4LeaseTimeError = null);
}
else {
setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid);
}
},
error: ipv4LeaseTimeError
),
],
if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[
SectionLabel(
label: AppLocalizations.of(context)!.ipv6settings,
padding: const EdgeInsets.all(16)
),
_DhcpField(
icon: Icons.skip_next_rounded,
label: AppLocalizations.of(context)!.startOfRange,
controller: ipv6StartRangeController,
onChanged: (value) => validateIpV6(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid),
error: ipv6StartRangeError
),
_DhcpField(
icon: Icons.skip_previous_rounded,
label: AppLocalizations.of(context)!.endOfRange,
controller: ipv6EndRangeController,
onChanged: (value) => validateIpV6(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid),
error: ipv6EndRangeError
),
_DhcpField(
icon: Icons.timer,
label: AppLocalizations.of(context)!.leaseTime,
controller: ipv6LeaseTimeController,
onChanged: (value) {
if (int.tryParse(value).runtimeType == int) {
setState(() => ipv6LeaseTimeError = null);
}
else {
setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid);
}
},
error: ipv6LeaseTimeError
)
],
const SizedBox(height: 20),
SectionLabel(
label: AppLocalizations.of(context)!.dhcpLeases,
padding: const EdgeInsets.all(16),
),
if (width <= 900) Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus!.leases,
staticLeases: false,
)
)
);
},
child: Container(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
AppLocalizations.of(context)!.dhcpLeases,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface,
),
),
Icon(
Icons.arrow_forward_rounded,
color: Theme.of(context).colorScheme.onSurface,
)
],
), ),
), ),
), ),
const SizedBox(height: 30), ),
ElevatedButton( if (width <= 900) Material(
onPressed: selectInterface, color: Colors.transparent,
child: Text(AppLocalizations.of(context)!.selectInterface) child: InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases,
staticLeases: true,
)
)
);
},
child: Container(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
AppLocalizations.of(context)!.dhcpStatic,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface,
),
),
Icon(
Icons.arrow_forward_rounded,
color: Theme.of(context).colorScheme.onSurface,
)
],
),
),
), ),
], ),
), if (width > 900) Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
if (!(Platform.isAndroid || Platform.isIOS)) {
SplitView.of(context).push(
DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus!.leases,
staticLeases: false,
)
);
}
else {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus!.leases,
staticLeases: false,
)
)
);
}
},
child: Row(
children: [
Text(AppLocalizations.of(context)!.dhcpLeases),
const SizedBox(width: 8),
const Icon(Icons.arrow_forward_rounded)
],
)
),
ElevatedButton(
onPressed: () {
if (!(Platform.isAndroid || Platform.isIOS)) {
SplitView.of(context).push(
DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases,
staticLeases: true,
)
);
}
else {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases,
staticLeases: true,
)
)
);
}
},
child: Row(
children: [
Text(AppLocalizations.of(context)!.dhcpStatic),
const SizedBox(width: 8),
const Icon(Icons.arrow_forward_rounded)
],
)
),
],
),
const SizedBox(height: 10)
],
), ),
], );
}
else {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Text(
AppLocalizations.of(context)!.neededSelectInterface,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5)
),
),
),
const SizedBox(height: 30),
ElevatedButton(
onPressed: selectInterface,
child: Text(AppLocalizations.of(context)!.selectInterface)
),
],
),
),
],
);
}
case LoadStatus.error:
return SizedBox(
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.dhcpSettingsNotLoaded,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
); );
}
case LoadStatus.error: default:
return SizedBox( return const SizedBox();
width: double.maxFinite, }
child: Column( },
mainAxisAlignment: MainAxisAlignment.center, ),
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.dhcpSettingsNotLoaded,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
default:
return const SizedBox();
}
},
) )
); );
} }

View file

@ -22,10 +22,10 @@ class DhcpLeases extends StatelessWidget {
final bool staticLeases; final bool staticLeases;
const DhcpLeases({ const DhcpLeases({
Key? key, super.key,
required this.items, required this.items,
required this.staticLeases, required this.staticLeases,
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -130,32 +130,34 @@ class DhcpLeases extends StatelessWidget {
), ),
), ),
body: items.isNotEmpty body: items.isNotEmpty
? ListView.builder( ? SafeArea(
padding: const EdgeInsets.only(top: 0), child: ListView.builder(
itemCount: items.length, padding: const EdgeInsets.only(top: 0),
itemBuilder: (context, index) => ListTile( itemCount: items.length,
isThreeLine: true, itemBuilder: (context, index) => ListTile(
title: Text(items[index].ip), isThreeLine: true,
subtitle: Column( title: Text(items[index].ip),
crossAxisAlignment: CrossAxisAlignment.start, subtitle: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
Text(items[index].mac), children: [
Text(items[index].hostname), Text(items[index].mac),
], Text(items[index].hostname),
],
),
trailing: staticLeases == true
? IconButton(
onPressed: () {
showModal(
context: context,
builder: (context) => DeleteStaticLeaseModal(
onConfirm: () => deleteLease(items[index])
)
);
},
icon: const Icon(Icons.delete)
)
: null,
), ),
trailing: staticLeases == true
? IconButton(
onPressed: () {
showModal(
context: context,
builder: (context) => DeleteStaticLeaseModal(
onConfirm: () => deleteLease(items[index])
)
);
},
icon: const Icon(Icons.delete)
)
: null,
), ),
) )
: Center( : Center(

View file

@ -137,16 +137,17 @@ class SelectInterfaceModal extends StatelessWidget {
), ),
), ),
Expanded( Expanded(
child: ListView.builder( child: SafeArea(
controller: controller, child: ListView.builder(
itemCount: interfaces.length, controller: controller,
itemBuilder: (context, index) => DhcpInterfaceItem( itemCount: interfaces.length,
networkInterface: interfaces[index], itemBuilder: (context, index) => DhcpInterfaceItem(
onSelect: onSelect networkInterface: interfaces[index],
) onSelect: onSelect
)
),
) )
), ),
const SizedBox(height: 16)
], ],
), ),
); );

View file

@ -117,105 +117,107 @@ class _BootstrapDnsScreenState extends State<BootstrapDnsScreen> {
const SizedBox(width: 10) const SizedBox(width: 10)
], ],
), ),
body: ListView( body: SafeArea(
padding: const EdgeInsets.only(top: 10), child: ListView(
children: [ padding: const EdgeInsets.only(top: 10),
Card( children: [
margin: const EdgeInsets.only( Card(
left: 16, right: 16, bottom: 20 margin: const EdgeInsets.only(
), left: 16, right: 16, bottom: 20
child: Padding( ),
padding: const EdgeInsets.all(20), child: Padding(
child: Row( padding: const EdgeInsets.all(20),
children: [ child: Row(
Icon( children: [
Icons.info_rounded, Icon(
color: Theme.of(context).listTileTheme.iconColor, Icons.info_rounded,
), color: Theme.of(context).listTileTheme.iconColor,
const SizedBox(width: 20), ),
Flexible( const SizedBox(width: 20),
child: Text( Flexible(
AppLocalizations.of(context)!.bootstrapDnsServersInfo, child: Text(
style: TextStyle( AppLocalizations.of(context)!.bootstrapDnsServersInfo,
color: Theme.of(context).colorScheme.onSurface style: TextStyle(
), color: Theme.of(context).colorScheme.onSurface
),
)
) )
) ],
], ),
), ),
), ),
), const SizedBox(height: 10),
const SizedBox(height: 10), if (bootstrapControllers.isEmpty) Column(
if (bootstrapControllers.isEmpty) Column( children: [
children: [ Padding(
Padding( padding: const EdgeInsets.all(10),
padding: const EdgeInsets.all(10), child: Center(
child: Center( child: Text(
child: Text( AppLocalizations.of(context)!.noBootstrapDns,
AppLocalizations.of(context)!.noBootstrapDns, style: TextStyle(
style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant,
color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 16
fontSize: 16 ),
), ),
), ),
), ),
), const SizedBox(height: 20),
const SizedBox(height: 20),
],
),
...bootstrapControllers.map((c) => Padding(
padding: const EdgeInsets.only(
left: 16, right: 6, bottom: 20
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: TextFormField(
controller: c['controller'],
onChanged: (value) => validateIp(c, value),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.dns_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: c['error'],
labelText: AppLocalizations.of(context)!.dnsServer,
)
),
),
const SizedBox(width: 8),
IconButton(
onPressed: () {
setState(() => bootstrapControllers = bootstrapControllers.where((con) => con != c).toList());
checkValidValues();
},
icon: const Icon(Icons.remove_circle_outline)
)
], ],
), ),
)).toList(), ...bootstrapControllers.map((c) => Padding(
Row( padding: const EdgeInsets.only(
mainAxisAlignment: MainAxisAlignment.center, left: 16, right: 6, bottom: 20
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
setState(() => bootstrapControllers.add({
'controller': TextEditingController(),
'error': null
}));
checkValidValues();
},
icon: const Icon(Icons.add),
label: Text(AppLocalizations.of(context)!.addItem)
), ),
], child: Row(
), mainAxisAlignment: MainAxisAlignment.spaceBetween,
const SizedBox(height: 20) children: [
], Expanded(
child: TextFormField(
controller: c['controller'],
onChanged: (value) => validateIp(c, value),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.dns_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: c['error'],
labelText: AppLocalizations.of(context)!.dnsServer,
)
),
),
const SizedBox(width: 8),
IconButton(
onPressed: () {
setState(() => bootstrapControllers = bootstrapControllers.where((con) => con != c).toList());
checkValidValues();
},
icon: const Icon(Icons.remove_circle_outline)
)
],
),
)).toList(),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
setState(() => bootstrapControllers.add({
'controller': TextEditingController(),
'error': null
}));
checkValidValues();
},
icon: const Icon(Icons.add),
label: Text(AppLocalizations.of(context)!.addItem)
),
],
),
const SizedBox(height: 20)
],
),
), ),
); );
} }

View file

@ -169,81 +169,83 @@ class _CacheConfigDnsScreenState extends State<CacheConfigDnsScreen> {
const SizedBox(width: 10) const SizedBox(width: 10)
], ],
), ),
body: ListView( body: SafeArea(
padding: const EdgeInsets.only(top: 10), child: ListView(
children: [ padding: const EdgeInsets.only(top: 10),
numericField( children: [
controller: cacheSizeController, numericField(
label: AppLocalizations.of(context)!.cacheSize, controller: cacheSizeController,
helper: AppLocalizations.of(context)!.inBytes, label: AppLocalizations.of(context)!.cacheSize,
error: cacheSizeError, helper: AppLocalizations.of(context)!.inBytes,
onChanged: (value) { error: cacheSizeError,
if (int.tryParse(value) != null) { onChanged: (value) {
setState(() => cacheSizeError = null); if (int.tryParse(value) != null) {
setState(() => cacheSizeError = null);
}
else {
setState(() => cacheSizeError = AppLocalizations.of(context)!.valueNotNumber);
}
checkValidData();
} }
else { ),
setState(() => cacheSizeError = AppLocalizations.of(context)!.valueNotNumber); const SizedBox(height: 30),
numericField(
controller: overrideMinTtlController,
label: AppLocalizations.of(context)!.overrideMinimumTtl,
helper: AppLocalizations.of(context)!.overrideMinimumTtlDescription,
error: overrideMinTtlError,
onChanged: (value) {
if (int.tryParse(value) != null) {
setState(() => overrideMinTtlError = null);
}
else {
setState(() => overrideMinTtlError = AppLocalizations.of(context)!.valueNotNumber);
}
checkValidData();
} }
checkValidData(); ),
} const SizedBox(height: 30),
), numericField(
const SizedBox(height: 30), controller: overrideMaxTtlController,
numericField( label: AppLocalizations.of(context)!.overrideMaximumTtl,
controller: overrideMinTtlController, helper: AppLocalizations.of(context)!.overrideMaximumTtlDescription,
label: AppLocalizations.of(context)!.overrideMinimumTtl, error: overrideMaxTtlError,
helper: AppLocalizations.of(context)!.overrideMinimumTtlDescription, onChanged: (value) {
error: overrideMinTtlError, if (int.tryParse(value) != null) {
onChanged: (value) { setState(() => overrideMaxTtlError = null);
if (int.tryParse(value) != null) { }
setState(() => overrideMinTtlError = null); else {
setState(() => overrideMaxTtlError = AppLocalizations.of(context)!.valueNotNumber);
}
checkValidData();
} }
else { ),
setState(() => overrideMinTtlError = AppLocalizations.of(context)!.valueNotNumber); const SizedBox(height: 10),
} CustomSwitchListTile(
checkValidData(); value: optimisticCache,
} onChanged: (value) => setState(() => optimisticCache = value),
), title: AppLocalizations.of(context)!.optimisticCaching,
const SizedBox(height: 30), subtitle: AppLocalizations.of(context)!.optimisticCachingDescription,
numericField( ),
controller: overrideMaxTtlController, const SizedBox(height: 12),
label: AppLocalizations.of(context)!.overrideMaximumTtl, Row(
helper: AppLocalizations.of(context)!.overrideMaximumTtlDescription, mainAxisAlignment: MainAxisAlignment.center,
error: overrideMaxTtlError, children: [
onChanged: (value) { ElevatedButton.icon(
if (int.tryParse(value) != null) { onPressed: () => showDialog(
setState(() => overrideMaxTtlError = null); context: context,
} builder: (context) => ClearDnsCacheDialog(
else { onConfirm: clearCache
setState(() => overrideMaxTtlError = AppLocalizations.of(context)!.valueNotNumber); )
} ),
checkValidData(); icon: const Icon(Icons.delete_rounded),
} label: Text(AppLocalizations.of(context)!.clearDnsCache),
),
const SizedBox(height: 10),
CustomSwitchListTile(
value: optimisticCache,
onChanged: (value) => setState(() => optimisticCache = value),
title: AppLocalizations.of(context)!.optimisticCaching,
subtitle: AppLocalizations.of(context)!.optimisticCachingDescription,
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: () => showDialog(
context: context,
builder: (context) => ClearDnsCacheDialog(
onConfirm: clearCache
)
), ),
icon: const Icon(Icons.delete_rounded), ],
label: Text(AppLocalizations.of(context)!.clearDnsCache), ),
), const SizedBox(height: 16)
], ],
), ),
const SizedBox(height: 16)
],
), ),
); );
} }

View file

@ -1,141 +1,30 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class CommentModal extends StatefulWidget { class CommentModal extends StatelessWidget {
final String? comment; final String? comment;
final void Function(String) onConfirm; final void Function(String) onConfirm;
final bool dialog; final bool dialog;
const CommentModal({ const CommentModal({
Key? key, super.key,
this.comment, this.comment,
required this.onConfirm, required this.onConfirm,
required this.dialog required this.dialog
}) : super(key: key); });
@override
State<CommentModal> createState() => _CommentModalState();
}
class _CommentModalState extends State<CommentModal> {
final TextEditingController commentController = TextEditingController();
bool validData = false;
@override
void initState() {
if (widget.comment != null) {
commentController.text = widget.comment!.replaceFirst(RegExp(r'#(\s)?'), "");
}
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget content() { if (dialog == true) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: SingleChildScrollView(
child: Wrap(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 24),
child: Icon(
Icons.comment_rounded,
size: 24,
color: Theme.of(context).colorScheme.secondary,
),
),
const SizedBox(height: 16),
Text(
AppLocalizations.of(context)!.comment,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
color: Theme.of(context).colorScheme.onSurface
),
),
const SizedBox(height: 16),
],
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextFormField(
controller: commentController,
onChanged: (value) {
if (value != '') {
setState(() => validData = true);
}
else {
setState(() => validData = false);
}
},
decoration: InputDecoration(
prefixIcon: const Icon(Icons.comment_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
labelText: AppLocalizations.of(context)!.comment,
helperText: AppLocalizations.of(context)!.commentsDescription,
helperMaxLines: 3
)
),
),
],
),
),
),
Padding(
padding: const EdgeInsets.all(24),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.cancel)
),
const SizedBox(width: 20),
TextButton(
onPressed: validData == true
? () {
Navigator.pop(context);
widget.onConfirm("# ${commentController.text}");
}
: null,
child: Text(
AppLocalizations.of(context)!.confirm,
style: TextStyle(
color: validData == true
? Theme.of(context).colorScheme.primary
: Colors.grey
),
)
),
],
),
)
],
);
}
if (widget.dialog == true) {
return Dialog( return Dialog(
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints( constraints: const BoxConstraints(
maxWidth: 400 maxWidth: 400
), ),
child: content() child: _Content(
comment: comment,
onConfirm: onConfirm,
)
), ),
); );
} }
@ -150,9 +39,139 @@ class _CommentModalState extends State<CommentModal> {
), ),
color: Theme.of(context).dialogBackgroundColor color: Theme.of(context).dialogBackgroundColor
), ),
child: content() child: SafeArea(
child: _Content(
comment: comment,
onConfirm: onConfirm,
),
)
), ),
); );
} }
} }
} }
class _Content extends StatefulWidget {
final String? comment;
final void Function(String) onConfirm;
const _Content({
required this.comment,
required this.onConfirm
});
@override
State<_Content> createState() => __ContentState();
}
class __ContentState extends State<_Content> {
final TextEditingController commentController = TextEditingController();
bool validData = false;
@override
void initState() {
if (widget.comment != null) {
commentController.text = widget.comment!.replaceFirst(RegExp(r'#(\s)?'), "");
}
super.initState();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: SingleChildScrollView(
child: Wrap(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 24),
child: Icon(
Icons.comment_rounded,
size: 24,
color: Theme.of(context).colorScheme.secondary,
),
),
const SizedBox(height: 16),
Text(
AppLocalizations.of(context)!.comment,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
color: Theme.of(context).colorScheme.onSurface
),
),
const SizedBox(height: 16),
],
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextFormField(
controller: commentController,
onChanged: (value) {
if (value != '') {
setState(() => validData = true);
}
else {
setState(() => validData = false);
}
},
decoration: InputDecoration(
prefixIcon: const Icon(Icons.comment_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
labelText: AppLocalizations.of(context)!.comment,
helperText: AppLocalizations.of(context)!.commentsDescription,
helperMaxLines: 3
)
),
),
],
),
),
),
Padding(
padding: const EdgeInsets.all(24),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.cancel)
),
const SizedBox(width: 20),
TextButton(
onPressed: validData == true
? () {
Navigator.pop(context);
widget.onConfirm("# ${commentController.text}");
}
: null,
child: Text(
AppLocalizations.of(context)!.confirm,
style: TextStyle(
color: validData == true
? Theme.of(context).colorScheme.primary
: Colors.grey
),
)
),
],
),
)
],
);
}
}

View file

@ -25,9 +25,9 @@ class DnsSettings extends StatefulWidget {
final bool splitView; final bool splitView;
const DnsSettings({ const DnsSettings({
Key? key, super.key,
required this.splitView, required this.splitView,
}) : super(key: key); });
@override @override
State<DnsSettings> createState() => _DnsSettingsState(); State<DnsSettings> createState() => _DnsSettingsState();
@ -118,95 +118,97 @@ class _DnsSettingsState extends State<DnsSettings> {
const SizedBox(width: 10) const SizedBox(width: 10)
], ],
), ),
body: Builder( body: SafeArea(
builder: (context) { child: Builder(
switch (dnsProvider.loadStatus) { builder: (context) {
case LoadStatus.loading: switch (dnsProvider.loadStatus) {
return SizedBox( case LoadStatus.loading:
width: double.maxFinite, return SizedBox(
child: Column( width: double.maxFinite,
mainAxisAlignment: MainAxisAlignment.center, child: Column(
crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ crossAxisAlignment: CrossAxisAlignment.center,
const CircularProgressIndicator(), children: [
const SizedBox(height: 30), const CircularProgressIndicator(),
Text( const SizedBox(height: 30),
AppLocalizations.of(context)!.loadingDnsConfig, Text(
textAlign: TextAlign.center, AppLocalizations.of(context)!.loadingDnsConfig,
style: TextStyle( textAlign: TextAlign.center,
fontSize: 22, style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 22,
), color: Theme.of(context).colorScheme.onSurfaceVariant,
) ),
], )
) ],
); )
);
case LoadStatus.loaded: case LoadStatus.loaded:
return ListView( return ListView(
children: [
CustomListTile(
title: AppLocalizations.of(context)!.upstreamDns,
subtitle: AppLocalizations.of(context)!.upstreamDnsDescription,
onTap: () => navigate(const UpstreamDnsScreen()),
icon: Icons.upload_rounded,
),
CustomListTile(
title: AppLocalizations.of(context)!.bootstrapDns,
subtitle: AppLocalizations.of(context)!.bootstrapDnsDescription,
onTap: () => navigate(const BootstrapDnsScreen()),
icon: Icons.dns_rounded,
),
CustomListTile(
title: AppLocalizations.of(context)!.privateReverseDnsServers,
subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription,
onTap: () => navigate(const PrivateReverseDnsServersScreen()),
icon: Icons.person_rounded,
),
CustomListTile(
title: AppLocalizations.of(context)!.dnsServerSettings,
subtitle: AppLocalizations.of(context)!.dnsServerSettingsDescription,
onTap: () => navigate(const DnsServerSettingsScreen()),
icon: Icons.settings,
),
CustomListTile(
title: AppLocalizations.of(context)!.dnsCacheConfig,
subtitle: AppLocalizations.of(context)!.dnsCacheConfigDescription,
onTap: () => navigate(const CacheConfigDnsScreen()),
icon: Icons.storage_rounded,
),
],
);
case LoadStatus.error:
return SizedBox(
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const Icon( CustomListTile(
Icons.error, title: AppLocalizations.of(context)!.upstreamDns,
color: Colors.red, subtitle: AppLocalizations.of(context)!.upstreamDnsDescription,
size: 50, onTap: () => navigate(const UpstreamDnsScreen()),
icon: Icons.upload_rounded,
),
CustomListTile(
title: AppLocalizations.of(context)!.bootstrapDns,
subtitle: AppLocalizations.of(context)!.bootstrapDnsDescription,
onTap: () => navigate(const BootstrapDnsScreen()),
icon: Icons.dns_rounded,
),
CustomListTile(
title: AppLocalizations.of(context)!.privateReverseDnsServers,
subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription,
onTap: () => navigate(const PrivateReverseDnsServersScreen()),
icon: Icons.person_rounded,
),
CustomListTile(
title: AppLocalizations.of(context)!.dnsServerSettings,
subtitle: AppLocalizations.of(context)!.dnsServerSettingsDescription,
onTap: () => navigate(const DnsServerSettingsScreen()),
icon: Icons.settings,
),
CustomListTile(
title: AppLocalizations.of(context)!.dnsCacheConfig,
subtitle: AppLocalizations.of(context)!.dnsCacheConfigDescription,
onTap: () => navigate(const CacheConfigDnsScreen()),
icon: Icons.storage_rounded,
), ),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.dnsConfigNotLoaded,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
], ],
), );
);
default: case LoadStatus.error:
return const SizedBox(); return SizedBox(
} width: double.maxFinite,
}, child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.dnsConfigNotLoaded,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
default:
return const SizedBox();
}
},
),
) )
); );
} }

View file

@ -167,141 +167,143 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
const SizedBox(width: 10) const SizedBox(width: 10)
], ],
), ),
body: ListView( body: SafeArea(
padding: const EdgeInsets.only(top: 10), child: ListView(
children: [ padding: const EdgeInsets.only(top: 10),
Padding( children: [
padding: const EdgeInsets.symmetric(horizontal: 16),
child: TextFormField(
controller: limitRequestsController,
onChanged: (value) {
if (int.tryParse(value) != null) {
setState(() => limitRequestsError = null);
}
else {
setState(() => limitRequestsError = AppLocalizations.of(context)!.valueNotNumber);
}
validateData();
},
decoration: InputDecoration(
prefixIcon: const Icon(Icons.looks_one_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
labelText: AppLocalizations.of(context)!.limitRequestsSecond,
errorText: limitRequestsError
),
keyboardType: TextInputType.number,
),
),
const SizedBox(height: 10),
CustomSwitchListTile(
value: enableEdns,
onChanged: (value) => setState(() => enableEdns = value),
title: AppLocalizations.of(context)!.enableEdns,
subtitle: AppLocalizations.of(context)!.enableEdnsDescription,
),
CustomSwitchListTile(
value: enableDnssec,
onChanged: (value) => setState(() => enableDnssec = value),
title: AppLocalizations.of(context)!.enableDnssec,
subtitle: AppLocalizations.of(context)!.enableDnssecDescription,
),
CustomSwitchListTile(
value: disableIpv6Resolving,
onChanged: (value) => setState(() => disableIpv6Resolving = value),
title: AppLocalizations.of(context)!.disableResolvingIpv6,
subtitle: AppLocalizations.of(context)!.disableResolvingIpv6Description,
),
SectionLabel(label: AppLocalizations.of(context)!.blockingMode),
CustomRadioListTile(
groupValue: blockingMode,
value: "default",
radioBackgroundColor: Theme.of(context).dialogBackgroundColor,
title: AppLocalizations.of(context)!.defaultMode,
subtitle: AppLocalizations.of(context)!.defaultDescription,
onChanged: updateBlockingMode,
),
CustomRadioListTile(
groupValue: blockingMode,
value: "refused",
radioBackgroundColor: Theme.of(context).dialogBackgroundColor,
title: "REFUSED",
subtitle: AppLocalizations.of(context)!.refusedDescription,
onChanged: updateBlockingMode,
),
CustomRadioListTile(
groupValue: blockingMode,
value: "nxdomain",
radioBackgroundColor: Theme.of(context).dialogBackgroundColor,
title: "NXDOMAIN",
subtitle: AppLocalizations.of(context)!.nxdomainDescription,
onChanged: updateBlockingMode,
),
CustomRadioListTile(
groupValue: blockingMode,
value: "null_ip",
radioBackgroundColor: Theme.of(context).dialogBackgroundColor,
title: AppLocalizations.of(context)!.nullIp,
subtitle: AppLocalizations.of(context)!.nullIpDescription,
onChanged: updateBlockingMode,
),
CustomRadioListTile(
groupValue: blockingMode,
value: "custom_ip",
radioBackgroundColor: Theme.of(context).dialogBackgroundColor,
title: AppLocalizations.of(context)!.customIp,
subtitle: AppLocalizations.of(context)!.customIpDescription,
onChanged: updateBlockingMode,
),
const SizedBox(height: 10),
if (blockingMode == 'custom_ip') ...[
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 16),
child: TextFormField( child: TextFormField(
controller: ipv4controller, controller: limitRequestsController,
onChanged: validateIpv4, onChanged: (value) {
if (int.tryParse(value) != null) {
setState(() => limitRequestsError = null);
}
else {
setState(() => limitRequestsError = AppLocalizations.of(context)!.valueNotNumber);
}
validateData();
},
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded), prefixIcon: const Icon(Icons.looks_one_rounded),
border: const OutlineInputBorder( border: const OutlineInputBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(10) Radius.circular(10)
) )
), ),
errorText: ipv4error, labelText: AppLocalizations.of(context)!.limitRequestsSecond,
helperText: AppLocalizations.of(context)!.blockingIpv4Description, errorText: limitRequestsError
helperMaxLines: 10,
labelText: AppLocalizations.of(context)!.blockingIpv4,
), ),
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
), ),
), ),
const SizedBox(height: 30), const SizedBox(height: 10),
Padding( CustomSwitchListTile(
padding: const EdgeInsets.symmetric(horizontal: 24), value: enableEdns,
child: TextFormField( onChanged: (value) => setState(() => enableEdns = value),
controller: ipv6controller, title: AppLocalizations.of(context)!.enableEdns,
onChanged: validateIpv6, subtitle: AppLocalizations.of(context)!.enableEdnsDescription,
decoration: InputDecoration( ),
prefixIcon: const Icon(Icons.link_rounded), CustomSwitchListTile(
border: const OutlineInputBorder( value: enableDnssec,
borderRadius: BorderRadius.all( onChanged: (value) => setState(() => enableDnssec = value),
Radius.circular(10) title: AppLocalizations.of(context)!.enableDnssec,
) subtitle: AppLocalizations.of(context)!.enableDnssecDescription,
),
CustomSwitchListTile(
value: disableIpv6Resolving,
onChanged: (value) => setState(() => disableIpv6Resolving = value),
title: AppLocalizations.of(context)!.disableResolvingIpv6,
subtitle: AppLocalizations.of(context)!.disableResolvingIpv6Description,
),
SectionLabel(label: AppLocalizations.of(context)!.blockingMode),
CustomRadioListTile(
groupValue: blockingMode,
value: "default",
radioBackgroundColor: Theme.of(context).dialogBackgroundColor,
title: AppLocalizations.of(context)!.defaultMode,
subtitle: AppLocalizations.of(context)!.defaultDescription,
onChanged: updateBlockingMode,
),
CustomRadioListTile(
groupValue: blockingMode,
value: "refused",
radioBackgroundColor: Theme.of(context).dialogBackgroundColor,
title: "REFUSED",
subtitle: AppLocalizations.of(context)!.refusedDescription,
onChanged: updateBlockingMode,
),
CustomRadioListTile(
groupValue: blockingMode,
value: "nxdomain",
radioBackgroundColor: Theme.of(context).dialogBackgroundColor,
title: "NXDOMAIN",
subtitle: AppLocalizations.of(context)!.nxdomainDescription,
onChanged: updateBlockingMode,
),
CustomRadioListTile(
groupValue: blockingMode,
value: "null_ip",
radioBackgroundColor: Theme.of(context).dialogBackgroundColor,
title: AppLocalizations.of(context)!.nullIp,
subtitle: AppLocalizations.of(context)!.nullIpDescription,
onChanged: updateBlockingMode,
),
CustomRadioListTile(
groupValue: blockingMode,
value: "custom_ip",
radioBackgroundColor: Theme.of(context).dialogBackgroundColor,
title: AppLocalizations.of(context)!.customIp,
subtitle: AppLocalizations.of(context)!.customIpDescription,
onChanged: updateBlockingMode,
),
const SizedBox(height: 10),
if (blockingMode == 'custom_ip') ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextFormField(
controller: ipv4controller,
onChanged: validateIpv4,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv4error,
helperText: AppLocalizations.of(context)!.blockingIpv4Description,
helperMaxLines: 10,
labelText: AppLocalizations.of(context)!.blockingIpv4,
), ),
errorText: ipv6error, keyboardType: TextInputType.number,
helperText: AppLocalizations.of(context)!.blockingIpv6Description,
helperMaxLines: 10,
labelText: AppLocalizations.of(context)!.blockingIpv6,
), ),
keyboardType: TextInputType.number,
), ),
), const SizedBox(height: 30),
const SizedBox(height: 30) Padding(
] padding: const EdgeInsets.symmetric(horizontal: 24),
], child: TextFormField(
controller: ipv6controller,
onChanged: validateIpv6,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv6error,
helperText: AppLocalizations.of(context)!.blockingIpv6Description,
helperMaxLines: 10,
labelText: AppLocalizations.of(context)!.blockingIpv6,
),
keyboardType: TextInputType.number,
),
),
const SizedBox(height: 30)
]
],
),
), ),
); );
} }

View file

@ -13,7 +13,7 @@ import 'package:adguard_home_manager/functions/snackbar.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart';
class PrivateReverseDnsServersScreen extends StatefulWidget { class PrivateReverseDnsServersScreen extends StatefulWidget {
const PrivateReverseDnsServersScreen({Key? key}) : super(key: key); const PrivateReverseDnsServersScreen({super.key});
@override @override
State<PrivateReverseDnsServersScreen> createState() => _PrivateReverseDnsServersScreenState(); State<PrivateReverseDnsServersScreen> createState() => _PrivateReverseDnsServersScreenState();
@ -149,107 +149,40 @@ class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServer
const SizedBox(width: 10) const SizedBox(width: 10)
], ],
), ),
body: ListView( body: SafeArea(
padding: const EdgeInsets.only(top: 10), child: ListView(
children: [ padding: const EdgeInsets.only(top: 10),
Card( children: [
margin: const EdgeInsets.only( Card(
left: 16, right: 16, bottom: 10 margin: const EdgeInsets.only(
), left: 16, right: 16, bottom: 10
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(
Icons.info_rounded,
color: Theme.of(context).listTileTheme.iconColor,
),
const SizedBox(width: 16),
Flexible(
child: Text(
AppLocalizations.of(context)!.privateReverseDnsServersDescription,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface
),
)
)
],
), ),
), child: Padding(
), padding: const EdgeInsets.all(16),
if (editReverseResolvers == false) ...[ child: Row(
Padding( children: [
padding: const EdgeInsets.all(20), Icon(
child: Text( Icons.info_rounded,
"${AppLocalizations.of(context)!.reverseDnsDefault}:\n\n${defaultReverseResolvers.map((item) => item).join(', ').toString().replaceAll(RegExp(r'\(|\)'), '')}", color: Theme.of(context).listTileTheme.iconColor,
textAlign: TextAlign.center, ),
style: TextStyle( const SizedBox(width: 16),
color: Theme.of(context).colorScheme.onSurfaceVariant, Flexible(
fontSize: 16 child: Text(
AppLocalizations.of(context)!.privateReverseDnsServersDescription,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface
),
)
)
],
), ),
), ),
), ),
Padding( if (editReverseResolvers == false) ...[
padding: const EdgeInsets.only(top: 10, bottom: 20), Padding(
child: Row( padding: const EdgeInsets.all(20),
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: () {
setState(() => editReverseResolvers = true);
checkDataValid();
},
icon: const Icon(Icons.edit),
label: Text(AppLocalizations.of(context)!.edit)
),
],
),
)
],
if (editReverseResolvers == true) ...[
const SizedBox(height: 20),
...reverseResolversControllers.map((c) => Padding(
padding: const EdgeInsets.only(
left: 16, right: 6, bottom: 20
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: TextFormField(
controller: c['controller'],
onChanged: (value) => validateAddress(c, value),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.dns_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: c['error'],
labelText: AppLocalizations.of(context)!.serverAddress,
)
),
),
const SizedBox(width: 8),
IconButton(
onPressed: () {
setState(() => reverseResolversControllers = reverseResolversControllers.where((con) => con != c).toList());
checkDataValid();
},
icon: const Icon(Icons.remove_circle_outline)
)
],
),
)),
if (reverseResolversControllers.isEmpty) Padding(
padding: const EdgeInsets.only(
left: 20, right: 20, bottom: 20
),
child: Center(
child: Text( child: Text(
AppLocalizations.of(context)!.noServerAddressesAdded, "${AppLocalizations.of(context)!.reverseDnsDefault}:\n\n${defaultReverseResolvers.map((item) => item).join(', ').toString().replaceAll(RegExp(r'\(|\)'), '')}",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant, color: Theme.of(context).colorScheme.onSurfaceVariant,
@ -257,41 +190,110 @@ class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServer
), ),
), ),
), ),
), Padding(
Padding( padding: const EdgeInsets.only(top: 10, bottom: 20),
padding: const EdgeInsets.only(bottom: 20), child: Row(
child: Row( mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, children: [
children: [ ElevatedButton.icon(
ElevatedButton.icon( onPressed: () {
onPressed: () { setState(() => editReverseResolvers = true);
setState(() => reverseResolversControllers.add({ checkDataValid();
'controller': TextEditingController(), },
'error': null icon: const Icon(Icons.edit),
})); label: Text(AppLocalizations.of(context)!.edit)
checkDataValid(); ),
}, ],
icon: const Icon(Icons.add), ),
label: Text(AppLocalizations.of(context)!.addItem) )
],
if (editReverseResolvers == true) ...[
const SizedBox(height: 20),
...reverseResolversControllers.map((c) => Padding(
padding: const EdgeInsets.only(
left: 16, right: 6, bottom: 20
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: TextFormField(
controller: c['controller'],
onChanged: (value) => validateAddress(c, value),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.dns_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: c['error'],
labelText: AppLocalizations.of(context)!.serverAddress,
)
),
),
const SizedBox(width: 8),
IconButton(
onPressed: () {
setState(() => reverseResolversControllers = reverseResolversControllers.where((con) => con != c).toList());
checkDataValid();
},
icon: const Icon(Icons.remove_circle_outline)
)
],
),
)),
if (reverseResolversControllers.isEmpty) Padding(
padding: const EdgeInsets.only(
left: 20, right: 20, bottom: 20
),
child: Center(
child: Text(
AppLocalizations.of(context)!.noServerAddressesAdded,
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 16
),
), ),
], ),
), ),
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
setState(() => reverseResolversControllers.add({
'controller': TextEditingController(),
'error': null
}));
checkDataValid();
},
icon: const Icon(Icons.add),
label: Text(AppLocalizations.of(context)!.addItem)
),
],
),
),
],
CustomSwitchListTile(
value: usePrivateReverseDnsResolvers,
onChanged: (value) => setState(() => usePrivateReverseDnsResolvers = value),
title: AppLocalizations.of(context)!.usePrivateReverseDnsResolvers,
subtitle: AppLocalizations.of(context)!.usePrivateReverseDnsResolversDescription
),
CustomSwitchListTile(
value: enableReverseResolve,
onChanged: (value) => setState(() => enableReverseResolve = value),
title: AppLocalizations.of(context)!.enableReverseResolving,
subtitle: AppLocalizations.of(context)!.enableReverseResolvingDescription
), ),
], ],
CustomSwitchListTile( ),
value: usePrivateReverseDnsResolvers,
onChanged: (value) => setState(() => usePrivateReverseDnsResolvers = value),
title: AppLocalizations.of(context)!.usePrivateReverseDnsResolvers,
subtitle: AppLocalizations.of(context)!.usePrivateReverseDnsResolversDescription
),
CustomSwitchListTile(
value: enableReverseResolve,
onChanged: (value) => setState(() => enableReverseResolve = value),
title: AppLocalizations.of(context)!.enableReverseResolving,
subtitle: AppLocalizations.of(context)!.enableReverseResolvingDescription
),
],
), ),
); );
} }

View file

@ -189,129 +189,131 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
const SizedBox(width: 10) const SizedBox(width: 10)
], ],
), ),
body: ListView( body: SafeArea(
padding: const EdgeInsets.only(top: 10), child: ListView(
children: [ padding: const EdgeInsets.only(top: 10),
if (dnsServers.isEmpty) Column( children: [
children: [ if (dnsServers.isEmpty) Column(
Padding( children: [
padding: const EdgeInsets.all(10), Padding(
child: Center( padding: const EdgeInsets.all(10),
child: Text( child: Center(
AppLocalizations.of(context)!.noUpstreamDns, child: Text(
style: TextStyle( AppLocalizations.of(context)!.noUpstreamDns,
color: Theme.of(context).colorScheme.onSurfaceVariant, style: TextStyle(
fontSize: 16 color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 16
),
), ),
), ),
), ),
), const SizedBox(height: 20),
const SizedBox(height: 20),
],
),
...dnsServers.map((item) => Padding(
padding: const EdgeInsets.only(
left: 16, right: 6, bottom: 24
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (item['controller'] != null) Expanded(
child: TextFormField(
controller: item['controller'],
onChanged: (_) => checkValidValues(),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.dns_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
labelText: AppLocalizations.of(context)!.dnsServer,
)
),
),
const SizedBox(width: 8),
if (item['comment'] != null) Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item['comment'],
style: TextStyle(
fontSize: 16,
color: Theme.of(context).listTileTheme.iconColor
),
),
IconButton(
onPressed: () => openEditCommentModal(item, dnsServers.indexOf(item)),
icon: const Icon(Icons.edit),
tooltip: AppLocalizations.of(context)!.edit,
)
],
),
),
IconButton(
onPressed: () {
setState(() => dnsServers = dnsServers.where((i) => i != item).toList());
checkValidValues();
},
icon: const Icon(Icons.remove_circle_outline),
tooltip: AppLocalizations.of(context)!.remove,
),
const SizedBox(width: 4),
], ],
), ),
)).toList(), ...dnsServers.map((item) => Padding(
const SizedBox(height: 12), padding: const EdgeInsets.only(
Row( left: 16, right: 6, bottom: 24
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: openAddCommentModal,
icon: const Icon(Icons.add),
label: Text(AppLocalizations.of(context)!.comment)
), ),
ElevatedButton.icon( child: Row(
onPressed: () { mainAxisAlignment: MainAxisAlignment.spaceBetween,
setState(() => dnsServers.add({ children: [
'controller': TextEditingController() if (item['controller'] != null) Expanded(
})); child: TextFormField(
checkValidValues(); controller: item['controller'],
}, onChanged: (_) => checkValidValues(),
icon: const Icon(Icons.add), decoration: InputDecoration(
label: Text(AppLocalizations.of(context)!.address) prefixIcon: const Icon(Icons.dns_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
labelText: AppLocalizations.of(context)!.dnsServer,
)
),
),
const SizedBox(width: 8),
if (item['comment'] != null) Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item['comment'],
style: TextStyle(
fontSize: 16,
color: Theme.of(context).listTileTheme.iconColor
),
),
IconButton(
onPressed: () => openEditCommentModal(item, dnsServers.indexOf(item)),
icon: const Icon(Icons.edit),
tooltip: AppLocalizations.of(context)!.edit,
)
],
),
),
IconButton(
onPressed: () {
setState(() => dnsServers = dnsServers.where((i) => i != item).toList());
checkValidValues();
},
icon: const Icon(Icons.remove_circle_outline),
tooltip: AppLocalizations.of(context)!.remove,
),
const SizedBox(width: 4),
],
), ),
], )).toList(),
), const SizedBox(height: 12),
const SizedBox(height: 16), Row(
SectionLabel(label: AppLocalizations.of(context)!.dnsMode), mainAxisAlignment: MainAxisAlignment.spaceEvenly,
CustomRadioListTile( mainAxisSize: MainAxisSize.min,
groupValue: upstreamMode, children: [
value: "", ElevatedButton.icon(
radioBackgroundColor: Theme.of(context).dialogBackgroundColor, onPressed: openAddCommentModal,
title: AppLocalizations.of(context)!.loadBalancing, icon: const Icon(Icons.add),
subtitle: AppLocalizations.of(context)!.loadBalancingDescription, label: Text(AppLocalizations.of(context)!.comment)
onChanged: (value) => setState(() => upstreamMode = value), ),
), ElevatedButton.icon(
CustomRadioListTile( onPressed: () {
groupValue: upstreamMode, setState(() => dnsServers.add({
value: "parallel", 'controller': TextEditingController()
radioBackgroundColor: Theme.of(context).dialogBackgroundColor, }));
title: AppLocalizations.of(context)!.parallelRequests, checkValidValues();
subtitle: AppLocalizations.of(context)!.parallelRequestsDescription, },
onChanged: (value) => setState(() => upstreamMode = value), icon: const Icon(Icons.add),
), label: Text(AppLocalizations.of(context)!.address)
CustomRadioListTile( ),
groupValue: upstreamMode, ],
value: "fastest_addr", ),
radioBackgroundColor: Theme.of(context).dialogBackgroundColor, const SizedBox(height: 16),
title: AppLocalizations.of(context)!.fastestIpAddress, SectionLabel(label: AppLocalizations.of(context)!.dnsMode),
subtitle: AppLocalizations.of(context)!.fastestIpAddressDescription, CustomRadioListTile(
onChanged: (value) => setState(() => upstreamMode = value), groupValue: upstreamMode,
), value: "",
], radioBackgroundColor: Theme.of(context).dialogBackgroundColor,
title: AppLocalizations.of(context)!.loadBalancing,
subtitle: AppLocalizations.of(context)!.loadBalancingDescription,
onChanged: (value) => setState(() => upstreamMode = value),
),
CustomRadioListTile(
groupValue: upstreamMode,
value: "parallel",
radioBackgroundColor: Theme.of(context).dialogBackgroundColor,
title: AppLocalizations.of(context)!.parallelRequests,
subtitle: AppLocalizations.of(context)!.parallelRequestsDescription,
onChanged: (value) => setState(() => upstreamMode = value),
),
CustomRadioListTile(
groupValue: upstreamMode,
value: "fastest_addr",
radioBackgroundColor: Theme.of(context).dialogBackgroundColor,
title: AppLocalizations.of(context)!.fastestIpAddress,
subtitle: AppLocalizations.of(context)!.fastestIpAddressDescription,
onChanged: (value) => setState(() => upstreamMode = value),
),
],
),
), ),
); );
} }

View file

@ -46,10 +46,12 @@ class DnsRewriteModal extends StatelessWidget {
), ),
color: Theme.of(context).dialogBackgroundColor, color: Theme.of(context).dialogBackgroundColor,
), ),
child: _Content( child: SafeArea(
onConfirm: onConfirm, child: _Content(
onDelete: onDelete, onConfirm: onConfirm,
rule: rule, onDelete: onDelete,
rule: rule,
),
) )
), ),
); );

View file

@ -136,239 +136,241 @@ class _DnsRewritesScreenState extends State<DnsRewritesScreen> {
surfaceTintColor: isDesktop(width) ? Colors.transparent : null, surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
centerTitle: false, centerTitle: false,
), ),
body: Stack( body: SafeArea(
children: [ child: Stack(
Builder( children: [
builder: (context) { Builder(
switch (rewriteRulesProvider.loadStatus) { builder: (context) {
case LoadStatus.loading: switch (rewriteRulesProvider.loadStatus) {
return SizedBox( case LoadStatus.loading:
width: double.maxFinite, return SizedBox(
child: Column( width: double.maxFinite,
mainAxisAlignment: MainAxisAlignment.center, child: Column(
crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ crossAxisAlignment: CrossAxisAlignment.center,
const CircularProgressIndicator(), children: [
const SizedBox(height: 30), const CircularProgressIndicator(),
Text( const SizedBox(height: 30),
AppLocalizations.of(context)!.loadingRewriteRules, Text(
textAlign: TextAlign.center, AppLocalizations.of(context)!.loadingRewriteRules,
style: TextStyle( textAlign: TextAlign.center,
fontSize: 22, style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 22,
), color: Theme.of(context).colorScheme.onSurfaceVariant,
) ),
], )
), ],
); ),
);
case LoadStatus.loaded: case LoadStatus.loaded:
if (rewriteRulesProvider.rewriteRules!.isNotEmpty) { if (rewriteRulesProvider.rewriteRules!.isNotEmpty) {
return RefreshIndicator( return RefreshIndicator(
onRefresh: () async { onRefresh: () async {
final result = await rewriteRulesProvider.fetchRules(); final result = await rewriteRulesProvider.fetchRules();
if (result == false) { if (result == false) {
showSnacbkar( showSnacbkar(
appConfigProvider: appConfigProvider, appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.rewriteRulesNotLoaded, label: AppLocalizations.of(context)!.rewriteRulesNotLoaded,
color: Colors.red color: Colors.red
); );
} }
}, },
child: ListView.builder( child: ListView.builder(
controller: scrollController, controller: scrollController,
padding: const EdgeInsets.only(top: 0), padding: const EdgeInsets.only(top: 0),
itemCount: rewriteRulesProvider.rewriteRules!.length, itemCount: rewriteRulesProvider.rewriteRules!.length,
itemBuilder: (context, index) => Card( itemBuilder: (context, index) => Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: InkWell( child: InkWell(
onTap: () => { onTap: () => {
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
showDialog( showDialog(
context: context, context: context,
builder: (context) => DnsRewriteModal( builder: (context) => DnsRewriteModal(
onConfirm: updateRewriteRule, onConfirm: updateRewriteRule,
dialog: true, dialog: true,
rule: rewriteRulesProvider.rewriteRules![index], rule: rewriteRulesProvider.rewriteRules![index],
onDelete: (rule) => showDialog( onDelete: (rule) => showDialog(
context: context, context: context,
builder: (context) => DeleteDnsRewrite( builder: (context) => DeleteDnsRewrite(
onConfirm: () => deleteDnsRewrite(rule) onConfirm: () => deleteDnsRewrite(rule)
) )
),
),
)
}
else {
showModalBottomSheet(
context: context,
useRootNavigator: true,
builder: (context) => DnsRewriteModal(
onConfirm: updateRewriteRule,
dialog: false,
rule: rewriteRulesProvider.rewriteRules![index],
onDelete: (rule) => showDialog(
context: context,
builder: (context) => DeleteDnsRewrite(
onConfirm: () => deleteDnsRewrite(rule)
)
),
),
backgroundColor: Colors.transparent,
isScrollControlled: true,
)
}
},
borderRadius: BorderRadius.circular(10),
child: Padding(
padding: const EdgeInsets.only(
left: 16, top: 16, bottom: 16, right: 8
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
"${AppLocalizations.of(context)!.domain}: ",
style: TextStyle(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface
),
),
Text(
rewriteRulesProvider.rewriteRules![index].domain,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface
),
),
],
), ),
const SizedBox(height: 3), ),
Row(
children: [
Text(
"${AppLocalizations.of(context)!.answer}: ",
style: TextStyle(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface
),
),
Text(
rewriteRulesProvider.rewriteRules![index].answer,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface
),
),
],
),
],
),
Icon(
Icons.keyboard_arrow_right_rounded,
color: Theme.of(context).colorScheme.onSurfaceVariant,
) )
], }
else {
showModalBottomSheet(
context: context,
useRootNavigator: true,
builder: (context) => DnsRewriteModal(
onConfirm: updateRewriteRule,
dialog: false,
rule: rewriteRulesProvider.rewriteRules![index],
onDelete: (rule) => showDialog(
context: context,
builder: (context) => DeleteDnsRewrite(
onConfirm: () => deleteDnsRewrite(rule)
)
),
),
backgroundColor: Colors.transparent,
isScrollControlled: true,
)
}
},
borderRadius: BorderRadius.circular(10),
child: Padding(
padding: const EdgeInsets.only(
left: 16, top: 16, bottom: 16, right: 8
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
"${AppLocalizations.of(context)!.domain}: ",
style: TextStyle(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface
),
),
Text(
rewriteRulesProvider.rewriteRules![index].domain,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface
),
),
],
),
const SizedBox(height: 3),
Row(
children: [
Text(
"${AppLocalizations.of(context)!.answer}: ",
style: TextStyle(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface
),
),
Text(
rewriteRulesProvider.rewriteRules![index].answer,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface
),
),
],
),
],
),
Icon(
Icons.keyboard_arrow_right_rounded,
color: Theme.of(context).colorScheme.onSurfaceVariant,
)
],
),
), ),
), ),
), )
)
),
);
}
else {
return Center(
child: Text(
AppLocalizations.of(context)!.noRewriteRules,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
), );
); }
} else {
return Center(
case LoadStatus.error: child: Text(
return SizedBox( AppLocalizations.of(context)!.noRewriteRules,
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.rewriteRulesNotLoaded,
textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 22, fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
) ),
], );
), }
);
default: case LoadStatus.error:
return const SizedBox(); return SizedBox(
} width: double.maxFinite,
}, child: Column(
), mainAxisAlignment: MainAxisAlignment.center,
AnimatedPositioned( crossAxisAlignment: CrossAxisAlignment.center,
duration: const Duration(milliseconds: 100), children: [
curve: Curves.easeInOut, const Icon(
bottom: isVisible ? Icons.error,
appConfigProvider.showingSnackbar color: Colors.red,
? 70 : 20 size: 50,
: -70, ),
right: 20, const SizedBox(height: 30),
child: FloatingActionButton( Text(
onPressed: () => { AppLocalizations.of(context)!.rewriteRulesNotLoaded,
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { textAlign: TextAlign.center,
showDialog( style: TextStyle(
context: context, fontSize: 22,
builder: (context) => DnsRewriteModal( color: Theme.of(context).colorScheme.onSurfaceVariant,
onConfirm: addDnsRewrite, ),
dialog: true,
onDelete: (rule) => showDialog(
context: context,
builder: (context) => DeleteDnsRewrite(
onConfirm: () => deleteDnsRewrite(rule)
) )
), ],
), ),
) );
}
else { default:
showModalBottomSheet( return const SizedBox();
context: context, }
useRootNavigator: true, },
builder: (context) => DnsRewriteModal( ),
onConfirm: addDnsRewrite, AnimatedPositioned(
dialog: false, duration: const Duration(milliseconds: 100),
onDelete: (rule) => showDialog( curve: Curves.easeInOut,
context: context, bottom: isVisible ?
builder: (context) => DeleteDnsRewrite( appConfigProvider.showingSnackbar
onConfirm: () => deleteDnsRewrite(rule) ? 70 : 20
) : -70,
right: 20,
child: FloatingActionButton(
onPressed: () => {
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
showDialog(
context: context,
builder: (context) => DnsRewriteModal(
onConfirm: addDnsRewrite,
dialog: true,
onDelete: (rule) => showDialog(
context: context,
builder: (context) => DeleteDnsRewrite(
onConfirm: () => deleteDnsRewrite(rule)
)
),
), ),
), )
backgroundColor: Colors.transparent, }
isScrollControlled: true else {
) showModalBottomSheet(
} context: context,
}, useRootNavigator: true,
child: const Icon(Icons.add), builder: (context) => DnsRewriteModal(
), onConfirm: addDnsRewrite,
) dialog: false,
], onDelete: (rule) => showDialog(
context: context,
builder: (context) => DeleteDnsRewrite(
onConfirm: () => deleteDnsRewrite(rule)
)
),
),
backgroundColor: Colors.transparent,
isScrollControlled: true
)
}
},
child: const Icon(Icons.add),
),
)
],
),
), ),
); );
} }

View file

@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class ServerVersionNeeded extends StatelessWidget { class ServerVersionNeeded extends StatelessWidget {
final String version; final String version;
// ignore: use_super_parameters
const ServerVersionNeeded({ const ServerVersionNeeded({
Key? key, Key? key,
required this.version required this.version

View file

@ -283,376 +283,378 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
const SizedBox(width: 10), const SizedBox(width: 10),
], ],
), ),
body: Builder( body: SafeArea(
builder: (context) { child: Builder(
switch (loadStatus) { builder: (context) {
case LoadStatus.loading: switch (loadStatus) {
return SizedBox( case LoadStatus.loading:
width: double.maxFinite, return SizedBox(
child: Column( width: double.maxFinite,
mainAxisAlignment: MainAxisAlignment.center, child: Column(
crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ crossAxisAlignment: CrossAxisAlignment.center,
const CircularProgressIndicator(),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.loadingEncryptionSettings,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
)
);
case LoadStatus.loaded:
return ListView(
children: [
EncryptionMasterSwitch(
value: enabled,
onChange: (value) {
setState(() => enabled = value);
onEditValidate();
}
),
SectionLabel(
label: AppLocalizations.of(context)!.serverConfiguration,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
),
EncryptionTextField(
enabled: enabled,
controller: domainNameController,
icon: Icons.link_rounded,
onChanged: (value) {
setState(() => domainError = validateDomain(context, value));
onEditValidate();
},
errorText: domainError,
label: AppLocalizations.of(context)!.domainName,
helperText: AppLocalizations.of(context)!.domainNameDescription,
),
const SizedBox(height: 10),
CustomSwitchListTile(
value: redirectHttps,
onChanged: (value) {
setState(() => redirectHttps = value);
onEditValidate();
},
title: AppLocalizations.of(context)!.redirectHttps,
disabled: !enabled,
),
const SizedBox(height: 10),
Wrap(
children: [ children: [
FractionallySizedBox( const CircularProgressIndicator(),
widthFactor: width > 900 ? 0.33 : 1, const SizedBox(height: 30),
child: EncryptionTextField( Text(
enabled: enabled, AppLocalizations.of(context)!.loadingEncryptionSettings,
controller: httpsPortController, textAlign: TextAlign.center,
icon: Icons.numbers_rounded, style: TextStyle(
onChanged: (value) { fontSize: 22,
setState(() => httpsPortError = validatePort(context, value)); color: Theme.of(context).colorScheme.onSurfaceVariant,
onEditValidate();
},
errorText: httpsPortError,
label: AppLocalizations.of(context)!.httpsPort,
keyboardType: TextInputType.number,
), ),
), )
Padding( ],
padding: width <= 900 )
? const EdgeInsets.symmetric(vertical: 24) );
: const EdgeInsets.all(0),
child: FractionallySizedBox( case LoadStatus.loaded:
return ListView(
children: [
EncryptionMasterSwitch(
value: enabled,
onChange: (value) {
setState(() => enabled = value);
onEditValidate();
}
),
SectionLabel(
label: AppLocalizations.of(context)!.serverConfiguration,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
),
EncryptionTextField(
enabled: enabled,
controller: domainNameController,
icon: Icons.link_rounded,
onChanged: (value) {
setState(() => domainError = validateDomain(context, value));
onEditValidate();
},
errorText: domainError,
label: AppLocalizations.of(context)!.domainName,
helperText: AppLocalizations.of(context)!.domainNameDescription,
),
const SizedBox(height: 10),
CustomSwitchListTile(
value: redirectHttps,
onChanged: (value) {
setState(() => redirectHttps = value);
onEditValidate();
},
title: AppLocalizations.of(context)!.redirectHttps,
disabled: !enabled,
),
const SizedBox(height: 10),
Wrap(
children: [
FractionallySizedBox(
widthFactor: width > 900 ? 0.33 : 1, widthFactor: width > 900 ? 0.33 : 1,
child: EncryptionTextField( child: EncryptionTextField(
enabled: enabled, enabled: enabled,
controller: tlsPortController, controller: httpsPortController,
icon: Icons.numbers_rounded, icon: Icons.numbers_rounded,
onChanged: (value) { onChanged: (value) {
setState(() => tlsPortError = validatePort(context, value)); setState(() => httpsPortError = validatePort(context, value));
onEditValidate(); onEditValidate();
}, },
errorText: tlsPortError, errorText: httpsPortError,
label: AppLocalizations.of(context)!.tlsPort, label: AppLocalizations.of(context)!.httpsPort,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
), ),
), ),
), Padding(
FractionallySizedBox( padding: width <= 900
widthFactor: width > 900 ? 0.33 : 1, ? const EdgeInsets.symmetric(vertical: 24)
child: EncryptionTextField( : const EdgeInsets.all(0),
enabled: enabled, child: FractionallySizedBox(
controller: dnsOverQuicPortController, widthFactor: width > 900 ? 0.33 : 1,
icon: Icons.numbers_rounded, child: EncryptionTextField(
onChanged: (value) { enabled: enabled,
setState(() => dnsOverQuicPortError = validatePort(context, value)); controller: tlsPortController,
onEditValidate(); icon: Icons.numbers_rounded,
}, onChanged: (value) {
errorText: dnsOverQuicPortError, setState(() => tlsPortError = validatePort(context, value));
label: AppLocalizations.of(context)!.dnsOverQuicPort, onEditValidate();
keyboardType: TextInputType.number, },
errorText: tlsPortError,
label: AppLocalizations.of(context)!.tlsPort,
keyboardType: TextInputType.number,
),
),
),
FractionallySizedBox(
widthFactor: width > 900 ? 0.33 : 1,
child: EncryptionTextField(
enabled: enabled,
controller: dnsOverQuicPortController,
icon: Icons.numbers_rounded,
onChanged: (value) {
setState(() => dnsOverQuicPortError = validatePort(context, value));
onEditValidate();
},
errorText: dnsOverQuicPortError,
label: AppLocalizations.of(context)!.dnsOverQuicPort,
keyboardType: TextInputType.number,
),
),
],
),
SectionLabel(
label: AppLocalizations.of(context)!.certificates,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
),
Card(
margin: const EdgeInsets.symmetric(horizontal: 16),
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Icon(
Icons.info_rounded,
color: Theme.of(context).listTileTheme.iconColor,
),
const SizedBox(width: 20),
Flexible(
child: Text(
AppLocalizations.of(context)!.certificatesDescription,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
),
)
)
],
), ),
), ),
],
),
SectionLabel(
label: AppLocalizations.of(context)!.certificates,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
),
Card(
margin: const EdgeInsets.symmetric(horizontal: 16),
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Icon(
Icons.info_rounded,
color: Theme.of(context).listTileTheme.iconColor,
),
const SizedBox(width: 20),
Flexible(
child: Text(
AppLocalizations.of(context)!.certificatesDescription,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
),
)
)
],
),
), ),
),
const SizedBox(height: 20),
RadioListTile(
value: 0,
groupValue: certificateOption,
onChanged: enabled == true
? (value) {
setState(() => certificateOption = int.parse(value.toString()));
onEditValidate();
}
: null,
title: Text(
AppLocalizations.of(context)!.certificateFilePath,
style: const TextStyle(
fontWeight: FontWeight.normal
),
),
),
RadioListTile(
value: 1,
groupValue: certificateOption,
onChanged: enabled == true
? (value) {
setState(() => certificateOption = int.parse(value.toString()));
onEditValidate();
}
: null,
title: Text(
AppLocalizations.of(context)!.pasteCertificateContent,
style: const TextStyle(
fontWeight: FontWeight.normal
),
),
),
const SizedBox(height: 10),
if (certificateOption == 0) EncryptionTextField(
enabled: enabled,
controller: certificatePathController,
icon: Icons.description_rounded,
onChanged: (value) {
setState(() => certificatePathError = validatePath(context, value));
onEditValidate();
},
label: AppLocalizations.of(context)!.certificatePath,
errorText: certificatePathError,
),
if (certificateOption == 1) EncryptionTextField(
enabled: enabled,
controller: certificateContentController,
icon: Icons.description_rounded,
onChanged: (value) {
setState(() => certificateContentError = validateCertificate(context, value));
onEditValidate();
},
label: AppLocalizations.of(context)!.certificateContent,
errorText: certificateContentError,
multiline: true,
keyboardType: TextInputType.multiline,
),
if (certKeyValid != null && (certificateContentController.text != '' || certificatePathController.text != '')) ...[
const SizedBox(height: 20), const SizedBox(height: 20),
if (certKeyValid!.validChain != null) ...[ RadioListTile(
Status( value: 0,
valid: certKeyValid!.validChain ?? false, groupValue: certificateOption,
label: certKeyValid!.validChain == true onChanged: enabled == true
? AppLocalizations.of(context)!.validCertificateChain ? (value) {
: AppLocalizations.of(context)!.invalidCertificateChain, setState(() => certificateOption = int.parse(value.toString()));
onEditValidate();
}
: null,
title: Text(
AppLocalizations.of(context)!.certificateFilePath,
style: const TextStyle(
fontWeight: FontWeight.normal
),
), ),
const SizedBox(height: 10), ),
], RadioListTile(
if (certKeyValid!.subject != null) ...[ value: 1,
Status( groupValue: certificateOption,
onChanged: enabled == true
? (value) {
setState(() => certificateOption = int.parse(value.toString()));
onEditValidate();
}
: null,
title: Text(
AppLocalizations.of(context)!.pasteCertificateContent,
style: const TextStyle(
fontWeight: FontWeight.normal
),
),
),
const SizedBox(height: 10),
if (certificateOption == 0) EncryptionTextField(
enabled: enabled,
controller: certificatePathController,
icon: Icons.description_rounded,
onChanged: (value) {
setState(() => certificatePathError = validatePath(context, value));
onEditValidate();
},
label: AppLocalizations.of(context)!.certificatePath,
errorText: certificatePathError,
),
if (certificateOption == 1) EncryptionTextField(
enabled: enabled,
controller: certificateContentController,
icon: Icons.description_rounded,
onChanged: (value) {
setState(() => certificateContentError = validateCertificate(context, value));
onEditValidate();
},
label: AppLocalizations.of(context)!.certificateContent,
errorText: certificateContentError,
multiline: true,
keyboardType: TextInputType.multiline,
),
if (certKeyValid != null && (certificateContentController.text != '' || certificatePathController.text != '')) ...[
const SizedBox(height: 20),
if (certKeyValid!.validChain != null) ...[
Status(
valid: certKeyValid!.validChain ?? false,
label: certKeyValid!.validChain == true
? AppLocalizations.of(context)!.validCertificateChain
: AppLocalizations.of(context)!.invalidCertificateChain,
),
const SizedBox(height: 10),
],
if (certKeyValid!.subject != null) ...[
Status(
valid: true,
label: "${AppLocalizations.of(context)!.subject}: ${certKeyValid?.subject}"
),
const SizedBox(height: 10),
],
if (certKeyValid!.issuer != null) ...[
Status(
valid: true, valid: true,
label: "${AppLocalizations.of(context)!.subject}: ${certKeyValid?.subject}" label: "${AppLocalizations.of(context)!.issuer}: ${certKeyValid?.issuer}"
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
],
if (certKeyValid!.notAfter != null) ...[
Status(
valid: true,
label: "${AppLocalizations.of(context)!.expirationDate}: ${certKeyValid?.notAfter}"
),
const SizedBox(height: 10),
],
if (certKeyValid!.dnsNames != null) ...[
Status(
valid: true,
label: "${AppLocalizations.of(context)!.hostNames}: ${certKeyValid?.dnsNames?.join(', ')}"
),
const SizedBox(height: 10),
],
], ],
if (certKeyValid!.issuer != null) ...[ SectionLabel(
Status( label: AppLocalizations.of(context)!.privateKey,
valid: true, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
label: "${AppLocalizations.of(context)!.issuer}: ${certKeyValid?.issuer}"
), ),
const SizedBox(height: 10), RadioListTile(
], value: 0,
if (certKeyValid!.notAfter != null) ...[ groupValue: privateKeyOption,
Status( onChanged: enabled == true
valid: true, ? (value) {
label: "${AppLocalizations.of(context)!.expirationDate}: ${certKeyValid?.notAfter}" setState(() => privateKeyOption = int.parse(value.toString()));
), onEditValidate();
const SizedBox(height: 10), }
], : null,
if (certKeyValid!.dnsNames != null) ...[ title: Text(
Status( AppLocalizations.of(context)!.privateKeyFile,
valid: true, style: const TextStyle(
label: "${AppLocalizations.of(context)!.hostNames}: ${certKeyValid?.dnsNames?.join(', ')}" fontWeight: FontWeight.normal
), ),
const SizedBox(height: 10),
],
],
SectionLabel(
label: AppLocalizations.of(context)!.privateKey,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
),
RadioListTile(
value: 0,
groupValue: privateKeyOption,
onChanged: enabled == true
? (value) {
setState(() => privateKeyOption = int.parse(value.toString()));
onEditValidate();
}
: null,
title: Text(
AppLocalizations.of(context)!.privateKeyFile,
style: const TextStyle(
fontWeight: FontWeight.normal
), ),
), ),
), RadioListTile(
RadioListTile( value: 1,
value: 1, groupValue: privateKeyOption,
groupValue: privateKeyOption, onChanged: enabled == true
onChanged: enabled == true ? (value) {
? (value) { setState(() => privateKeyOption = int.parse(value.toString()));
setState(() => privateKeyOption = int.parse(value.toString())); onEditValidate();
onEditValidate(); }
} : null,
: null, title: Text(
title: Text( AppLocalizations.of(context)!.pastePrivateKey,
AppLocalizations.of(context)!.pastePrivateKey, style: const TextStyle(
style: const TextStyle( fontWeight: FontWeight.normal
fontWeight: FontWeight.normal ),
), ),
), ),
), if (privateKeyOption == 0) const SizedBox(height: 10),
if (privateKeyOption == 0) const SizedBox(height: 10), if (privateKeyOption == 1) ...[
if (privateKeyOption == 1) ...[ CustomSwitchListTile(
CustomSwitchListTile( value: usePreviouslySavedKey,
value: usePreviouslySavedKey, onChanged: (value) => setState(() => usePreviouslySavedKey = value),
onChanged: (value) => setState(() => usePreviouslySavedKey = value), title: AppLocalizations.of(context)!.usePreviousKey,
title: AppLocalizations.of(context)!.usePreviousKey,
),
const SizedBox(height: 10)
],
if (privateKeyOption == 0) EncryptionTextField(
enabled: enabled,
controller: privateKeyPathController,
icon: Icons.description_rounded,
onChanged: (value) {
setState(() => privateKeyPathError = validatePath(context, value));
onEditValidate();
},
label: AppLocalizations.of(context)!.privateKeyPath,
errorText: privateKeyPathError,
),
if (privateKeyOption == 1) EncryptionTextField(
enabled: enabled == true
? !usePreviouslySavedKey
: false,
controller: pastePrivateKeyController,
icon: Icons.description_rounded,
onChanged: (value) {
setState(() => pastePrivateKeyError = validatePrivateKey(context, value));
onEditValidate();
},
label: AppLocalizations.of(context)!.pastePrivateKey,
errorText: pastePrivateKeyError,
keyboardType: TextInputType.multiline,
multiline: true,
),
const SizedBox(height: 20),
if (certKeyValid != null && (privateKeyPathController.text != '' || pastePrivateKeyController.text != '' || usePreviouslySavedKey == true)) ...[
if (certKeyValid!.validKey != null) ...[
Status(
valid: certKeyValid!.validKey ?? false,
label: certKeyValid!.validKey == true
? AppLocalizations.of(context)!.validPrivateKey
: AppLocalizations.of(context)!.invalidPrivateKey,
), ),
const SizedBox(height: 10) const SizedBox(height: 10)
], ],
if (certKeyValid!.validPair != null && certKeyValid!.validPair == false) ...[ if (privateKeyOption == 0) EncryptionTextField(
Status( enabled: enabled,
valid: false, controller: privateKeyPathController,
label: AppLocalizations.of(context)!.keysNotMatch, icon: Icons.description_rounded,
), onChanged: (value) {
const SizedBox(height: 10) setState(() => privateKeyPathError = validatePath(context, value));
], onEditValidate();
if (certKeyValid!.keyType != null) ...[ },
Status( label: AppLocalizations.of(context)!.privateKeyPath,
valid: true, errorText: privateKeyPathError,
label: "${AppLocalizations.of(context)!.keyType}: ${certKeyValid!.keyType}"
),
const SizedBox(height: 10),
],
const SizedBox(height: 10)
]
],
);
case LoadStatus.error:
return SizedBox(
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
), ),
const SizedBox(height: 30), if (privateKeyOption == 1) EncryptionTextField(
Text( enabled: enabled == true
AppLocalizations.of(context)!.encryptionSettingsNotLoaded, ? !usePreviouslySavedKey
textAlign: TextAlign.center, : false,
style: TextStyle( controller: pastePrivateKeyController,
fontSize: 22, icon: Icons.description_rounded,
color: Theme.of(context).colorScheme.onSurfaceVariant, onChanged: (value) {
), setState(() => pastePrivateKeyError = validatePrivateKey(context, value));
) onEditValidate();
},
label: AppLocalizations.of(context)!.pastePrivateKey,
errorText: pastePrivateKeyError,
keyboardType: TextInputType.multiline,
multiline: true,
),
const SizedBox(height: 20),
if (certKeyValid != null && (privateKeyPathController.text != '' || pastePrivateKeyController.text != '' || usePreviouslySavedKey == true)) ...[
if (certKeyValid!.validKey != null) ...[
Status(
valid: certKeyValid!.validKey ?? false,
label: certKeyValid!.validKey == true
? AppLocalizations.of(context)!.validPrivateKey
: AppLocalizations.of(context)!.invalidPrivateKey,
),
const SizedBox(height: 10)
],
if (certKeyValid!.validPair != null && certKeyValid!.validPair == false) ...[
Status(
valid: false,
label: AppLocalizations.of(context)!.keysNotMatch,
),
const SizedBox(height: 10)
],
if (certKeyValid!.keyType != null) ...[
Status(
valid: true,
label: "${AppLocalizations.of(context)!.keyType}: ${certKeyValid!.keyType}"
),
const SizedBox(height: 10),
],
const SizedBox(height: 10)
]
], ],
), );
);
default: case LoadStatus.error:
return const SizedBox(); return SizedBox(
} width: double.maxFinite,
}, child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.encryptionSettingsNotLoaded,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
default:
return const SizedBox();
}
},
),
) )
); );
} }

View file

@ -124,173 +124,175 @@ class _GeneralSettingsState extends State<GeneralSettings> {
title: Text(AppLocalizations.of(context)!.generalSettings), title: Text(AppLocalizations.of(context)!.generalSettings),
surfaceTintColor: isDesktop(width) ? Colors.transparent : null, surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
), ),
body: ListView( body: SafeArea(
children: [ child: ListView(
SectionLabel(label: AppLocalizations.of(context)!.home), children: [
CustomListTile( SectionLabel(label: AppLocalizations.of(context)!.home),
icon: Icons.exposure_zero_rounded, CustomListTile(
title: AppLocalizations.of(context)!.hideZeroValues, icon: Icons.exposure_zero_rounded,
subtitle: AppLocalizations.of(context)!.hideZeroValuesDescription, title: AppLocalizations.of(context)!.hideZeroValues,
trailing: Switch( subtitle: AppLocalizations.of(context)!.hideZeroValuesDescription,
value: appConfigProvider.hideZeroValues, trailing: Switch(
onChanged: (value) => updateSettings( value: appConfigProvider.hideZeroValues,
newStatus: value, onChanged: (value) => updateSettings(
newStatus: value,
function: appConfigProvider.setHideZeroValues
),
),
onTap: () => updateSettings(
newStatus: !appConfigProvider.hideZeroValues,
function: appConfigProvider.setHideZeroValues function: appConfigProvider.setHideZeroValues
), ),
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
left: 16,
right: 10
)
), ),
onTap: () => updateSettings( CustomListTile(
newStatus: !appConfigProvider.hideZeroValues, icon: Icons.show_chart_rounded,
function: appConfigProvider.setHideZeroValues title: AppLocalizations.of(context)!.combinedChart,
), subtitle: AppLocalizations.of(context)!.combinedChartDescription,
padding: const EdgeInsets.only( trailing: Switch(
top: 10, value: appConfigProvider.combinedChartHome,
bottom: 10, onChanged: (value) => updateSettings(
left: 16, newStatus: value,
right: 10 function: appConfigProvider.setCombinedChartHome
) ),
), ),
CustomListTile( onTap: () => updateSettings(
icon: Icons.show_chart_rounded, newStatus: !appConfigProvider.combinedChartHome,
title: AppLocalizations.of(context)!.combinedChart,
subtitle: AppLocalizations.of(context)!.combinedChartDescription,
trailing: Switch(
value: appConfigProvider.combinedChartHome,
onChanged: (value) => updateSettings(
newStatus: value,
function: appConfigProvider.setCombinedChartHome function: appConfigProvider.setCombinedChartHome
), ),
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
left: 16,
right: 10
)
), ),
onTap: () => updateSettings( CustomListTile(
newStatus: !appConfigProvider.combinedChartHome, icon: Icons.remove_red_eye_rounded,
function: appConfigProvider.setCombinedChartHome title: AppLocalizations.of(context)!.hideServerAddress,
), subtitle: AppLocalizations.of(context)!.hideServerAddressDescription,
padding: const EdgeInsets.only( trailing: Switch(
top: 10, value: appConfigProvider.hideServerAddress,
bottom: 10, onChanged: (value) => updateSettings(
left: 16, newStatus: value,
right: 10 function: appConfigProvider.setHideServerAddress
) ),
), ),
CustomListTile( onTap: () => updateSettings(
icon: Icons.remove_red_eye_rounded, newStatus: !appConfigProvider.hideServerAddress,
title: AppLocalizations.of(context)!.hideServerAddress,
subtitle: AppLocalizations.of(context)!.hideServerAddressDescription,
trailing: Switch(
value: appConfigProvider.hideServerAddress,
onChanged: (value) => updateSettings(
newStatus: value,
function: appConfigProvider.setHideServerAddress function: appConfigProvider.setHideServerAddress
), ),
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
left: 16,
right: 10
)
), ),
onTap: () => updateSettings( CustomListTile(
newStatus: !appConfigProvider.hideServerAddress, icon: Icons.reorder_rounded,
function: appConfigProvider.setHideServerAddress title: AppLocalizations.of(context)!.topItemsOrder,
), subtitle: AppLocalizations.of(context)!.topItemsOrderDescription,
padding: const EdgeInsets.only( onTap: () => widget.splitView == true
top: 10, ? SplitView.of(context).push(const ReorderableTopItemsHome())
bottom: 10, : Navigator.of(context).push(
left: 16, MaterialPageRoute(
right: 10 builder: (context) => const ReorderableTopItemsHome()
) )
),
CustomListTile(
icon: Icons.reorder_rounded,
title: AppLocalizations.of(context)!.topItemsOrder,
subtitle: AppLocalizations.of(context)!.topItemsOrderDescription,
onTap: () => widget.splitView == true
? SplitView.of(context).push(const ReorderableTopItemsHome())
: Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const ReorderableTopItemsHome()
) )
) ),
), CustomListTile(
CustomListTile( icon: Icons.donut_large_rounded,
icon: Icons.donut_large_rounded, title: AppLocalizations.of(context)!.showTopItemsChart,
title: AppLocalizations.of(context)!.showTopItemsChart, subtitle: AppLocalizations.of(context)!.showTopItemsChartDescription,
subtitle: AppLocalizations.of(context)!.showTopItemsChartDescription, trailing: Switch(
trailing: Switch( value: appConfigProvider.showTopItemsChart,
value: appConfigProvider.showTopItemsChart, onChanged: (value) => updateSettings(
onChanged: (value) => updateSettings( newStatus: value,
newStatus: value, function: appConfigProvider.setShowTopItemsChart
),
),
onTap: () => updateSettings(
newStatus: !appConfigProvider.showTopItemsChart,
function: appConfigProvider.setShowTopItemsChart function: appConfigProvider.setShowTopItemsChart
), ),
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
left: 16,
right: 10
)
), ),
onTap: () => updateSettings( SectionLabel(label: AppLocalizations.of(context)!.logs),
newStatus: !appConfigProvider.showTopItemsChart, CustomListTile(
function: appConfigProvider.setShowTopItemsChart icon: Icons.timer_rounded,
), title: AppLocalizations.of(context)!.timeLogs,
padding: const EdgeInsets.only( subtitle: AppLocalizations.of(context)!.timeLogsDescription,
top: 10, trailing: Switch(
bottom: 10, value: appConfigProvider.showTimeLogs,
left: 16, onChanged: (value) => updateSettings(
right: 10 newStatus: value,
) function: appConfigProvider.setshowTimeLogs
), ),
SectionLabel(label: AppLocalizations.of(context)!.logs), ),
CustomListTile( onTap: () => updateSettings(
icon: Icons.timer_rounded, newStatus: !appConfigProvider.showTimeLogs,
title: AppLocalizations.of(context)!.timeLogs,
subtitle: AppLocalizations.of(context)!.timeLogsDescription,
trailing: Switch(
value: appConfigProvider.showTimeLogs,
onChanged: (value) => updateSettings(
newStatus: value,
function: appConfigProvider.setshowTimeLogs function: appConfigProvider.setshowTimeLogs
), ),
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
left: 16,
right: 10
)
), ),
onTap: () => updateSettings( CustomListTile(
newStatus: !appConfigProvider.showTimeLogs, icon: Icons.more,
function: appConfigProvider.setshowTimeLogs title: AppLocalizations.of(context)!.ipLogs,
), subtitle: AppLocalizations.of(context)!.ipLogsDescription,
padding: const EdgeInsets.only( trailing: Switch(
top: 10, value: appConfigProvider.showIpLogs,
bottom: 10, onChanged: (value) => updateSettings(
left: 16, newStatus: value,
right: 10 function: appConfigProvider.setShowIpLogs
) ),
), ),
CustomListTile( onTap: () => updateSettings(
icon: Icons.more, newStatus: !appConfigProvider.showIpLogs,
title: AppLocalizations.of(context)!.ipLogs,
subtitle: AppLocalizations.of(context)!.ipLogsDescription,
trailing: Switch(
value: appConfigProvider.showIpLogs,
onChanged: (value) => updateSettings(
newStatus: value,
function: appConfigProvider.setShowIpLogs function: appConfigProvider.setShowIpLogs
), ),
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
left: 16,
right: 10
)
), ),
onTap: () => updateSettings( if (
newStatus: !appConfigProvider.showIpLogs, !(Platform.isAndroid || Platform.isIOS) ||
function: appConfigProvider.setShowIpLogs (Platform.isAndroid && (
), appConfigProvider.installationSource == Source.IS_INSTALLED_FROM_LOCAL_SOURCE ||
padding: const EdgeInsets.only( appConfigProvider.installationSource == Source.IS_INSTALLED_FROM_PLAY_PACKAGE_INSTALLER ||
top: 10, appConfigProvider.installationSource == Source.UNKNOWN
bottom: 10, ))
left: 16, ) ...[
right: 10 SectionLabel(label: AppLocalizations.of(context)!.application),
) CustomListTile(
), icon: Icons.system_update_rounded,
if ( title: AppLocalizations.of(context)!.appUpdates,
!(Platform.isAndroid || Platform.isIOS) || subtitle: appConfigProvider.appUpdatesAvailable != null
(Platform.isAndroid && ( ? AppLocalizations.of(context)!.updateAvailable
appConfigProvider.installationSource == Source.IS_INSTALLED_FROM_LOCAL_SOURCE || : AppLocalizations.of(context)!.usingLatestVersion,
appConfigProvider.installationSource == Source.IS_INSTALLED_FROM_PLAY_PACKAGE_INSTALLER || trailing: generateAppUpdateStatus()
appConfigProvider.installationSource == Source.UNKNOWN )
)) ]
) ...[ ],
SectionLabel(label: AppLocalizations.of(context)!.application), ),
CustomListTile(
icon: Icons.system_update_rounded,
title: AppLocalizations.of(context)!.appUpdates,
subtitle: appConfigProvider.appUpdatesAvailable != null
? AppLocalizations.of(context)!.updateAvailable
: AppLocalizations.of(context)!.usingLatestVersion,
trailing: generateAppUpdateStatus()
)
]
],
) )
); );
} }

View file

@ -14,7 +14,7 @@ import 'package:adguard_home_manager/functions/snackbar.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart';
class SafeSearchSettingsScreen extends StatefulWidget { class SafeSearchSettingsScreen extends StatefulWidget {
const SafeSearchSettingsScreen({Key? key}) : super(key: key); const SafeSearchSettingsScreen({super.key});
@override @override
State<SafeSearchSettingsScreen> createState() => _SafeSearchSettingsScreenState(); State<SafeSearchSettingsScreen> createState() => _SafeSearchSettingsScreenState();
@ -123,166 +123,168 @@ class _SafeSearchSettingsScreenState extends State<SafeSearchSettingsScreen> {
const SizedBox(width: 8) const SizedBox(width: 8)
], ],
), ),
body: Builder( body: SafeArea(
builder: (context) { child: Builder(
switch (statusProvider.loadStatus) { builder: (context) {
case LoadStatus.loading: switch (statusProvider.loadStatus) {
return SizedBox( case LoadStatus.loading:
width: double.maxFinite, return SizedBox(
child: Column( width: double.maxFinite,
mainAxisAlignment: MainAxisAlignment.center, child: Column(
crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ crossAxisAlignment: CrossAxisAlignment.center,
const CircularProgressIndicator(), children: [
const SizedBox(height: 30), const CircularProgressIndicator(),
Text( const SizedBox(height: 30),
AppLocalizations.of(context)!.loadingSafeSearchSettings, Text(
textAlign: TextAlign.center, AppLocalizations.of(context)!.loadingSafeSearchSettings,
style: TextStyle( textAlign: TextAlign.center,
fontSize: 22, style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 22,
), color: Theme.of(context).colorScheme.onSurfaceVariant,
) ),
], )
), ],
); ),
);
case LoadStatus.loaded: case LoadStatus.loaded:
return RefreshIndicator( return RefreshIndicator(
onRefresh: requestSafeSearchSettings, onRefresh: requestSafeSearchSettings,
child: ListView( child: ListView(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 16, top: 16,
left: 16, left: 16,
right: 16, right: 16,
bottom: 8 bottom: 8
), ),
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
borderRadius: BorderRadius.circular(28),
child: InkWell(
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
onTap: () => setState(() => generalEnabled = !generalEnabled), child: InkWell(
child: Container( borderRadius: BorderRadius.circular(28),
padding: const EdgeInsets.symmetric( onTap: () => setState(() => generalEnabled = !generalEnabled),
horizontal: 24, child: Container(
vertical: 12 padding: const EdgeInsets.symmetric(
), horizontal: 24,
decoration: BoxDecoration( vertical: 12
color: Theme.of(context).colorScheme.primary.withOpacity(0.1), ),
borderRadius: BorderRadius.circular(28) decoration: BoxDecoration(
), color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
child: Row( borderRadius: BorderRadius.circular(28)
mainAxisAlignment: MainAxisAlignment.spaceBetween, ),
children: [ child: Row(
Flexible( mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: Text( children: [
AppLocalizations.of(context)!.enableSafeSearch, Flexible(
style: const TextStyle( child: Text(
fontSize: 18 AppLocalizations.of(context)!.enableSafeSearch,
style: const TextStyle(
fontSize: 18
),
), ),
), ),
), Switch(
Switch( value: generalEnabled,
value: generalEnabled, onChanged: (value) => setState(() => generalEnabled = value)
onChanged: (value) => setState(() => generalEnabled = value) )
) ],
], ),
), ),
), ),
), ),
), ),
), CustomCheckboxListTile(
CustomCheckboxListTile( value: bingEnabled,
value: bingEnabled, onChanged: (value) => setState(() => bingEnabled = value),
onChanged: (value) => setState(() => bingEnabled = value), title: "Bing",
title: "Bing", padding: const EdgeInsets.only(
padding: const EdgeInsets.only( top: 8, left: 40, right: 40, bottom: 8
top: 8, left: 40, right: 40, bottom: 8 ),
disabled: !generalEnabled,
), ),
disabled: !generalEnabled, CustomCheckboxListTile(
), value: duckduckgoEnabled,
CustomCheckboxListTile( onChanged: (value) => setState(() => duckduckgoEnabled = value),
value: duckduckgoEnabled, title: "DuckDuckGo",
onChanged: (value) => setState(() => duckduckgoEnabled = value), padding: const EdgeInsets.only(
title: "DuckDuckGo", top: 8, left: 40, right: 40, bottom: 8
padding: const EdgeInsets.only( ),
top: 8, left: 40, right: 40, bottom: 8 disabled: !generalEnabled,
), ),
disabled: !generalEnabled, CustomCheckboxListTile(
), value: googleEnabled,
CustomCheckboxListTile( onChanged: (value) => setState(() => googleEnabled = value),
value: googleEnabled, title: "Google",
onChanged: (value) => setState(() => googleEnabled = value), padding: const EdgeInsets.only(
title: "Google", top: 8, left: 40, right: 40, bottom: 8
padding: const EdgeInsets.only( ),
top: 8, left: 40, right: 40, bottom: 8 disabled: !generalEnabled,
), ),
disabled: !generalEnabled, CustomCheckboxListTile(
), value: pixabayEnabled,
CustomCheckboxListTile( onChanged: (value) => setState(() => pixabayEnabled = value),
value: pixabayEnabled, title: "Pixabay",
onChanged: (value) => setState(() => pixabayEnabled = value), padding: const EdgeInsets.only(
title: "Pixabay", top: 8, left: 40, right: 40, bottom: 8
padding: const EdgeInsets.only( ),
top: 8, left: 40, right: 40, bottom: 8 disabled: !generalEnabled,
), ),
disabled: !generalEnabled, CustomCheckboxListTile(
), value: yandexEnabled,
CustomCheckboxListTile( onChanged: (value) => setState(() => yandexEnabled = value),
value: yandexEnabled, title: "Yandex",
onChanged: (value) => setState(() => yandexEnabled = value), padding: const EdgeInsets.only(
title: "Yandex", top: 8, left: 40, right: 40, bottom: 8
padding: const EdgeInsets.only( ),
top: 8, left: 40, right: 40, bottom: 8 disabled: !generalEnabled,
), ),
disabled: !generalEnabled, CustomCheckboxListTile(
), value: youtubeEnabled,
CustomCheckboxListTile( onChanged: (value) => setState(() => youtubeEnabled = value),
value: youtubeEnabled, title: "YouTube",
onChanged: (value) => setState(() => youtubeEnabled = value), padding: const EdgeInsets.only(
title: "YouTube", top: 8, left: 40, right: 40, bottom: 8
padding: const EdgeInsets.only( ),
top: 8, left: 40, right: 40, bottom: 8 disabled: !generalEnabled,
), ),
disabled: !generalEnabled, ],
), ),
], );
),
);
case LoadStatus.error: case LoadStatus.error:
return SizedBox( return SizedBox(
width: double.maxFinite, width: double.maxFinite,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const Icon( const Icon(
Icons.error, Icons.error,
color: Colors.red, color: Colors.red,
size: 50, size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.safeSearchSettingsNotLoaded,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
) const SizedBox(height: 30),
], Text(
), AppLocalizations.of(context)!.safeSearchSettingsNotLoaded,
); textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
default: default:
return const SizedBox(); return const SizedBox();
} }
}, },
),
) )
); );
} }

View file

@ -51,114 +51,116 @@ class _ServerInformationState extends State<ServerInformation> {
surfaceTintColor: isDesktop(width) ? Colors.transparent : null, surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
centerTitle: false, centerTitle: false,
), ),
body: Builder( body: SafeArea(
builder: (context) { child: Builder(
switch (serverInfo.loadStatus) { builder: (context) {
case LoadStatus.loading: switch (serverInfo.loadStatus) {
return SizedBox( case LoadStatus.loading:
width: double.maxFinite, return SizedBox(
child: Column( width: double.maxFinite,
mainAxisAlignment: MainAxisAlignment.center, child: Column(
crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Text(
AppLocalizations.of(context)!.loadingServerInfo,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
)
],
),
);
case LoadStatus.loaded:
return ListView(
children: [ children: [
const CircularProgressIndicator(), CustomListTile(
const SizedBox(height: 30), title: AppLocalizations.of(context)!.dnsAddresses,
Padding( subtitle: AppLocalizations.of(context)!.seeDnsAddresses,
padding: const EdgeInsets.symmetric(horizontal: 20), onTap: () {
child: Text( showModal(
AppLocalizations.of(context)!.loadingServerInfo, context: context,
builder: (context) => DnsAddressesModal(
dnsAddresses: serverInfo.data!.dnsAddresses
)
);
},
),
CustomListTile(
title: AppLocalizations.of(context)!.dnsPort,
subtitle: serverInfo.data!.dnsPort.toString(),
),
CustomListTile(
title: AppLocalizations.of(context)!.httpPort,
subtitle: serverInfo.data!.httpPort.toString(),
),
CustomListTile(
title: AppLocalizations.of(context)!.protectionEnabled,
subtitle: serverInfo.data!.protectionEnabled == true
? AppLocalizations.of(context)!.yes
: AppLocalizations.of(context)!.no,
),
CustomListTile(
title: AppLocalizations.of(context)!.dhcpAvailable,
subtitle: serverInfo.data!.dhcpAvailable == true
? AppLocalizations.of(context)!.yes
: AppLocalizations.of(context)!.no,
),
CustomListTile(
title: AppLocalizations.of(context)!.serverRunning,
subtitle: serverInfo.data!.running == true
? AppLocalizations.of(context)!.yes
: AppLocalizations.of(context)!.no,
),
CustomListTile(
title: AppLocalizations.of(context)!.serverVersion,
subtitle: serverInfo.data!.version,
),
CustomListTile(
title: AppLocalizations.of(context)!.serverLanguage,
subtitle: serverInfo.data!.language,
),
]
);
case LoadStatus.error:
return SizedBox(
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.serverInfoNotLoaded,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 22, fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
), )
) ],
], ),
), );
);
case LoadStatus.loaded: default:
return ListView( return const SizedBox();
children: [ }
CustomListTile( },
title: AppLocalizations.of(context)!.dnsAddresses, ),
subtitle: AppLocalizations.of(context)!.seeDnsAddresses,
onTap: () {
showModal(
context: context,
builder: (context) => DnsAddressesModal(
dnsAddresses: serverInfo.data!.dnsAddresses
)
);
},
),
CustomListTile(
title: AppLocalizations.of(context)!.dnsPort,
subtitle: serverInfo.data!.dnsPort.toString(),
),
CustomListTile(
title: AppLocalizations.of(context)!.httpPort,
subtitle: serverInfo.data!.httpPort.toString(),
),
CustomListTile(
title: AppLocalizations.of(context)!.protectionEnabled,
subtitle: serverInfo.data!.protectionEnabled == true
? AppLocalizations.of(context)!.yes
: AppLocalizations.of(context)!.no,
),
CustomListTile(
title: AppLocalizations.of(context)!.dhcpAvailable,
subtitle: serverInfo.data!.dhcpAvailable == true
? AppLocalizations.of(context)!.yes
: AppLocalizations.of(context)!.no,
),
CustomListTile(
title: AppLocalizations.of(context)!.serverRunning,
subtitle: serverInfo.data!.running == true
? AppLocalizations.of(context)!.yes
: AppLocalizations.of(context)!.no,
),
CustomListTile(
title: AppLocalizations.of(context)!.serverVersion,
subtitle: serverInfo.data!.version,
),
CustomListTile(
title: AppLocalizations.of(context)!.serverLanguage,
subtitle: serverInfo.data!.language,
),
]
);
case LoadStatus.error:
return SizedBox(
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.serverInfoNotLoaded,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
default:
return const SizedBox();
}
},
) )
); );
} }

View file

@ -14,10 +14,10 @@ class ThemeModal extends StatefulWidget {
final int selectedTheme; final int selectedTheme;
const ThemeModal({ const ThemeModal({
Key? key, super.key,
required this.statusBarHeight, required this.statusBarHeight,
required this.selectedTheme, required this.selectedTheme,
}) : super(key: key); });
@override @override
State<ThemeModal> createState() => _ThemeModalState(); State<ThemeModal> createState() => _ThemeModalState();

View file

@ -17,7 +17,7 @@ import 'package:adguard_home_manager/constants/enums.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart';
class UpdateScreen extends StatelessWidget { class UpdateScreen extends StatelessWidget {
const UpdateScreen({Key? key}) : super(key: key); const UpdateScreen({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -166,33 +166,38 @@ class UpdateScreen extends StatelessWidget {
); );
} }
final changelog = serversProvider.updateAvailable.loadStatus == LoadStatus.loaded && serversProvider.updateAvailable.data!.changelog != null final SafeArea? changelog;
? ListView( if (serversProvider.updateAvailable.loadStatus == LoadStatus.loaded && serversProvider.updateAvailable.data!.changelog != null) {
children: [ changelog = SafeArea(
Padding( child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 16), children: [
child: Text( Padding(
"Changelog ${serversProvider.updateAvailable.data!.canAutoupdate == true padding: const EdgeInsets.symmetric(horizontal: 16),
? serversProvider.updateAvailable.data!.newVersion child: Text(
: serversProvider.updateAvailable.data!.currentVersion}", "Changelog ${serversProvider.updateAvailable.data!.canAutoupdate == true
style: TextStyle( ? serversProvider.updateAvailable.data!.newVersion
fontSize: 20, : serversProvider.updateAvailable.data!.currentVersion}",
fontWeight: FontWeight.w500, style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant fontSize: 20,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant
),
), ),
), ),
), const SizedBox(height: 8),
const SizedBox(height: 8), Padding(
Padding( padding: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(horizontal: 16), child: Html(
child: Html( data: html.parse(md.markdownToHtml(serversProvider.updateAvailable.data!.changelog!)).outerHtml,
data: html.parse(md.markdownToHtml(serversProvider.updateAvailable.data!.changelog!)).outerHtml, onLinkTap: (url, context, attributes) => url != null ? openUrl(url) : null,
onLinkTap: (url, context, attributes) => url != null ? openUrl(url) : null, )
) )
) ],
], ),
) );
: null; } else {
changelog = null;
}
return Scaffold( return Scaffold(
body: Column( body: Column(

View file

@ -550,8 +550,10 @@ class _AddServerModalState extends State<AddServerModal> {
const SizedBox(width: 8) const SizedBox(width: 8)
], ],
), ),
body: ListView( body: SafeArea(
children: form() child: ListView(
children: form()
),
), ),
), ),
); );

View file

@ -5,12 +5,14 @@ class OverlayStyle extends StatelessWidget {
final Widget child; final Widget child;
const OverlayStyle({ const OverlayStyle({
Key? key, super.key,
required this.child required this.child
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final systemGestureInsets = MediaQuery.of(context).systemGestureInsets;
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarColor: Colors.transparent, statusBarColor: Colors.transparent,
@ -20,7 +22,13 @@ class OverlayStyle extends StatelessWidget {
statusBarIconBrightness: Theme.of(context).brightness == Brightness.light statusBarIconBrightness: Theme.of(context).brightness == Brightness.light
? Brightness.dark ? Brightness.dark
: Brightness.light, : Brightness.light,
systemNavigationBarColor: Theme.of(context).colorScheme.background, systemNavigationBarColor: systemGestureInsets.left > 0 // If true gestures navigation
? Colors.transparent
: ElevationOverlay.applySurfaceTint(
Theme.of(context).colorScheme.surface,
Theme.of(context).colorScheme.surfaceTint,
3
),
systemNavigationBarIconBrightness: Theme.of(context).brightness == Brightness.light systemNavigationBarIconBrightness: Theme.of(context).brightness == Brightness.light
? Brightness.dark ? Brightness.dark
: Brightness.light, : Brightness.light,

View file

@ -19,7 +19,7 @@ class CustomTabContentList extends StatelessWidget {
final EdgeInsets? listPadding; final EdgeInsets? listPadding;
const CustomTabContentList({ const CustomTabContentList({
Key? key, super.key,
required this.loadingGenerator, required this.loadingGenerator,
required this.itemsCount, required this.itemsCount,
required this.contentWidget, required this.contentWidget,
@ -32,7 +32,7 @@ class CustomTabContentList extends StatelessWidget {
this.fabVisible, this.fabVisible,
this.noSliver, this.noSliver,
this.listPadding this.listPadding
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -49,7 +49,6 @@ class CustomTabContentList extends StatelessWidget {
else { else {
return SafeArea( return SafeArea(
top: false, top: false,
bottom: false,
child: Builder( child: Builder(
builder: (BuildContext context) => CustomScrollView( builder: (BuildContext context) => CustomScrollView(
slivers: [ slivers: [
@ -72,41 +71,45 @@ class CustomTabContentList extends StatelessWidget {
case LoadStatus.loaded: case LoadStatus.loaded:
if (noSliver == true) { if (noSliver == true) {
if (itemsCount > 0) { if (itemsCount > 0) {
return Stack( return SafeArea(
children: [ child: Stack(
ListView.builder( children: [
padding: listPadding, ListView.builder(
itemCount: itemsCount, padding: listPadding,
itemBuilder: (context, index) => contentWidget(index), itemCount: itemsCount,
), itemBuilder: (context, index) => contentWidget(index),
if (fab != null) AnimatedPositioned( ),
duration: const Duration(milliseconds: 100), if (fab != null) AnimatedPositioned(
curve: Curves.easeInOut, duration: const Duration(milliseconds: 100),
bottom: fabVisible != null && fabVisible == true ? curve: Curves.easeInOut,
appConfigProvider.showingSnackbar bottom: fabVisible != null && fabVisible == true ?
? 70 : 20 appConfigProvider.showingSnackbar
: -70, ? 70 : 20
right: 20, : -70,
child: fab! right: 20,
), child: fab!
], ),
],
),
); );
} }
else { else {
return Stack( return SafeArea(
children: [ child: Stack(
noData, children: [
if (fab != null) AnimatedPositioned( noData,
duration: const Duration(milliseconds: 100), if (fab != null) AnimatedPositioned(
curve: Curves.easeInOut, duration: const Duration(milliseconds: 100),
bottom: fabVisible != null && fabVisible == true ? curve: Curves.easeInOut,
appConfigProvider.showingSnackbar bottom: fabVisible != null && fabVisible == true ?
? 70 : 20 appConfigProvider.showingSnackbar
: -70, ? 70 : 20
right: 20, : -70,
child: fab! right: 20,
), child: fab!
], ),
],
),
); );
} }
} }
@ -146,10 +149,10 @@ class CustomTabContentList extends StatelessWidget {
curve: Curves.easeInOut, curve: Curves.easeInOut,
bottom: fabVisible != null && fabVisible == true ? bottom: fabVisible != null && fabVisible == true ?
appConfigProvider.showingSnackbar appConfigProvider.showingSnackbar
? 70 : 20 ? 90 : 20
: -70, : -90,
right: 20, right: 20,
child: fab! child: SafeArea(child: fab!)
), ),
], ],
); );
@ -169,7 +172,6 @@ class CustomTabContentList extends StatelessWidget {
else { else {
return SafeArea( return SafeArea(
top: false, top: false,
bottom: false,
child: Builder( child: Builder(
builder: (BuildContext context) => CustomScrollView( builder: (BuildContext context) => CustomScrollView(
slivers: [ slivers: [