adguard-home-manager/lib/screens/settings/statistics_settings/statistics_settings.dart
Juan Gilsanz Polo 7bb7ad40c4 Fixed some bugs
2024-09-11 22:32:26 +02:00

338 lines
No EOL
13 KiB
Dart

import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/screens/settings/logs_settings/logs_settings.dart';
import 'package:adguard_home_manager/widgets/load_status_widgets.dart';
import 'package:adguard_home_manager/widgets/section_label.dart';
import 'package:adguard_home_manager/widgets/master_switch.dart';
import 'package:adguard_home_manager/classes/process_modal.dart';
import 'package:adguard_home_manager/functions/snackbar.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart';
import 'package:adguard_home_manager/models/statistics_config.dart';
import 'package:adguard_home_manager/constants/enums.dart';
class StatisticsSettings extends StatefulWidget {
const StatisticsSettings({super.key});
@override
State<StatisticsSettings> createState() => _StatisticsSettingsState();
}
class _StatisticsSettingsState extends State<StatisticsSettings> {
final Uuid uuid = const Uuid();
LoadStatus _loadStatus = LoadStatus.loading;
bool _generalSwitch = false;
final List<String> _retentionItems = [
"custom",
"86400000",
"604800000",
"2592000000",
"7776000000"
];
final _customTimeController = TextEditingController();
String? _customTimeError;
String? _retentionTime;
List<DomainListItemController> _ignoredDomainsControllers = [];
void loadData() async {
final serversProvider = Provider.of<ServersProvider>(context, listen: false);
final result = await serversProvider.apiClient2!.getStatisticsConfig();
if (!mounted) return;
if (result.successful == true) {
final data = result.content as StatisticsConfig;
setState(() {
_generalSwitch = data.enabled ?? false;
if (_retentionItems.contains(data.interval.toString())) {
_retentionTime = data.interval.toString();
}
else if (data.interval != null) {
_retentionTime = "custom";
_customTimeController.text = Duration(milliseconds: data.interval!).inHours.toString();
}
if (data.ignored != null) {
_ignoredDomainsControllers = data.ignored!.map((e) => DomainListItemController(
id: uuid.v4(),
controller: TextEditingController(text: e),
error: false
)).toList();
}
_loadStatus = LoadStatus.loaded;
});
}
else {
setState(() => _loadStatus = LoadStatus.error);
}
}
@override
void initState() {
loadData();
super.initState();
}
@override
Widget build(BuildContext context) {
final serversProvider = Provider.of<ServersProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context);
final List<String> dropdownItemTranslation = [
AppLocalizations.of(context)!.custom,
AppLocalizations.of(context)!.hours24,
AppLocalizations.of(context)!.days7,
AppLocalizations.of(context)!.days30,
AppLocalizations.of(context)!.days90,
];
void validateDomain(String value, String id) {
final domainRegex = RegExp(r'^([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+$');
bool error = false;
if (domainRegex.hasMatch(value)) {
error = false;
}
else {
error = true;
}
setState(() {
_ignoredDomainsControllers = _ignoredDomainsControllers.map((entry) {
if (entry.id != id) return entry;
return DomainListItemController(
id: id,
controller: entry.controller,
error: error
);
}).toList();
});
}
void validateCustomTime(String v) {
try {
final regex = RegExp(r'^\d+$');
final parsed = int.parse(v);
if (!regex.hasMatch(v)) {
setState(() => _customTimeError = AppLocalizations.of(context)!.invalidTime);
}
else if (parsed < 1) {
setState(() => _customTimeError = AppLocalizations.of(context)!.notLess1Hour);
}
else {
setState(() => _customTimeError = null);
}
} catch (_) {
setState(() => _customTimeError = AppLocalizations.of(context)!.invalidTime);
}
}
void updateConfig() async {
ProcessModal processModal = ProcessModal();
processModal.open(AppLocalizations.of(context)!.updatingSettings);
final result = await serversProvider.apiClient2!.updateStatisticsSettings(
body: {
"enabled": _generalSwitch,
"interval": _retentionTime == "custom"
? Duration(hours: int.parse(_customTimeController.text)).inMilliseconds
: int.parse(_retentionTime ?? _retentionItems[0]),
"ignored": _ignoredDomainsControllers.map((e) => e.controller.text).toList()
}
);
processModal.close();
if (!context.mounted) return;
if (result.successful == true) {
showSnackbar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.statisticsConfigUpdated,
color: Colors.green
);
}
else {
showSnackbar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.statisticsConfigNotUpdated,
color: Colors.red
);
}
}
final validValues = _ignoredDomainsControllers.where(
(d) => d.controller.text == "" || d.error == true
).isEmpty &&
(_retentionTime != "custom" ||
(_retentionTime == "custom" && _customTimeController.text != "" && _customTimeError == null));
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.statisticsSettings),
actions: [
IconButton(
onPressed: validValues ? () => updateConfig() : null,
icon: const Icon(Icons.save_rounded),
tooltip: AppLocalizations.of(context)!.save,
),
const SizedBox(width: 8)
],
),
body: Builder(
builder: (context) {
switch (_loadStatus) {
case LoadStatus.loading:
return LoadingData(text: AppLocalizations.of(context)!.loadingStatisticsSettings);
case LoadStatus.loaded:
return ListView(
children: [
const SizedBox(height: 8),
MasterSwitch(
label: AppLocalizations.of(context)!.enableLog,
value: _generalSwitch,
onChange: (v) => setState(() => _generalSwitch = v)
),
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: DropdownButtonFormField(
items: _retentionItems.asMap().entries.map((item) => DropdownMenuItem(
value: item.value,
child: Text(dropdownItemTranslation[item.key]),
)).toList(),
value: _retentionTime,
onChanged: (value) => setState(() {
if (value != null && value != "custom") {
_customTimeError = null;
_customTimeController.text = "";
}
_retentionTime = value;
}),
decoration: InputDecoration(
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
label: Text(AppLocalizations.of(context)!.retentionTime)
),
borderRadius: BorderRadius.circular(20),
),
),
if (_retentionTime == "custom") Padding(
padding: const EdgeInsets.only(
left: 16,
right: 16,
top: 24
),
child: TextFormField(
controller: _customTimeController,
onChanged: validateCustomTime,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.schedule_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
labelText: AppLocalizations.of(context)!.customTimeInHours,
errorText: _customTimeError
),
keyboardType: TextInputType.number,
),
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SectionLabel(
label: AppLocalizations.of(context)!.ignoredDomains,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 0),
),
Padding(
padding: const EdgeInsets.only(right: 6),
child: IconButton(
onPressed: () => setState(() => _ignoredDomainsControllers = [
..._ignoredDomainsControllers,
DomainListItemController(
id: uuid.v4(),
controller: TextEditingController(),
error: false
),
]),
icon: const Icon(Icons.add),
tooltip: AppLocalizations.of(context)!.addDomain,
),
)
],
),
),
if (_ignoredDomainsControllers.isNotEmpty) ..._ignoredDomainsControllers.map((controller) => Padding(
padding: const EdgeInsets.only(
top: 12, bottom: 12, left: 16, right: 6
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: TextFormField(
controller: controller.controller,
onChanged: (v) => validateDomain(v, controller.id),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
labelText: AppLocalizations.of(context)!.domain,
errorText: controller.error
? AppLocalizations.of(context)!.invalidDomain
: null
),
),
),
const SizedBox(width: 12),
Padding(
padding: controller.error
? const EdgeInsets.only(bottom: 24)
: const EdgeInsets.all(0),
child: IconButton(
onPressed: () => setState(() => _ignoredDomainsControllers = _ignoredDomainsControllers.where((e) => e.id != controller.id).toList()),
icon: const Icon(Icons.remove_circle_outline_outlined),
tooltip: AppLocalizations.of(context)!.removeDomain,
),
)
],
),
)),
if (_ignoredDomainsControllers.isEmpty) Container(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Text(
AppLocalizations.of(context)!.noIgnoredDomainsAdded,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
],
);
case LoadStatus.error:
return ErrorLoadData(text: AppLocalizations.of(context)!.statisticsSettingsLoadError);
default:
return const SizedBox();
}
},
),
);
}
}