mirror of
https://github.com/JGeek00/adguard-home-manager.git
synced 2025-05-04 20:30:35 +00:00
Merge remote-tracking branch 'origin/master' into russian_locale
# Conflicts: # lib/main.dart
This commit is contained in:
commit
d0e77bc4ff
252 changed files with 19911 additions and 13707 deletions
116
lib/base.dart
116
lib/base.dart
|
@ -1,116 +0,0 @@
|
|||
// ignore_for_file: use_build_context_synchronously, depend_on_referenced_packages
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/bottom_nav_bar.dart';
|
||||
import 'package:adguard_home_manager/widgets/menu_bar.dart';
|
||||
import 'package:adguard_home_manager/widgets/update_modal.dart';
|
||||
import 'package:adguard_home_manager/widgets/navigation_rail.dart';
|
||||
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
import 'package:adguard_home_manager/functions/check_app_updates.dart';
|
||||
import 'package:adguard_home_manager/functions/open_url.dart';
|
||||
import 'package:adguard_home_manager/models/app_screen.dart';
|
||||
import 'package:adguard_home_manager/config/app_screens.dart';
|
||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||
|
||||
class Base extends StatefulWidget {
|
||||
const Base({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<Base> createState() => _BaseState();
|
||||
}
|
||||
|
||||
class _BaseState extends State<Base> with WidgetsBindingObserver {
|
||||
int selectedScreen = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context, listen: false);
|
||||
final result = await checkAppUpdates(
|
||||
currentBuildNumber: appConfigProvider.getAppInfo!.buildNumber,
|
||||
installationSource: appConfigProvider.installationSource,
|
||||
setUpdateAvailable: appConfigProvider.setAppUpdatesAvailable,
|
||||
isBeta: appConfigProvider.getAppInfo!.version.contains('beta'),
|
||||
);
|
||||
|
||||
if (result != null && appConfigProvider.doNotRememberVersion != result.tagName) {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => UpdateModal(
|
||||
gitHubRelease: result,
|
||||
onDownload: (link, version) => openUrl(link),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
List<AppScreen> screens = serversProvider.selectedServer != null
|
||||
? screensServerConnected
|
||||
: screensSelectServer;
|
||||
|
||||
if (kDebugMode && dotenv.env['ENABLE_SENTRY'] == "true") {
|
||||
Sentry.captureMessage("Debug mode");
|
||||
}
|
||||
|
||||
return CustomMenuBar(
|
||||
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarBrightness: Theme.of(context).brightness == Brightness.light
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
statusBarIconBrightness: Theme.of(context).brightness == Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
systemNavigationBarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
systemNavigationBarIconBrightness: Theme.of(context).brightness == Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
),
|
||||
child: Scaffold(
|
||||
body: Row(
|
||||
children: [
|
||||
if (width > 900) const SideNavigationRail(),
|
||||
Expanded(
|
||||
child: PageTransitionSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
transitionBuilder: (
|
||||
(child, primaryAnimation, secondaryAnimation) => FadeThroughTransition(
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
child: child,
|
||||
)
|
||||
),
|
||||
child: screens[appConfigProvider.selectedScreen].body,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: width <= 900
|
||||
? const BottomNavBar()
|
||||
: null,
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
216
lib/classes/http_client.dart
Normal file
216
lib/classes/http_client.dart
Normal file
|
@ -0,0 +1,216 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:adguard_home_manager/models/server.dart';
|
||||
|
||||
enum ExceptionType { socket, timeout, handshake, http, unknown }
|
||||
|
||||
class HttpResponse {
|
||||
final bool successful;
|
||||
final String? body;
|
||||
final int? statusCode;
|
||||
final ExceptionType? exception;
|
||||
|
||||
const HttpResponse({
|
||||
required this.successful,
|
||||
required this.body,
|
||||
required this.statusCode,
|
||||
this.exception,
|
||||
});
|
||||
}
|
||||
|
||||
String getConnectionString({
|
||||
required Server server,
|
||||
required String urlPath,
|
||||
}) {
|
||||
return "${server.connectionMethod}://${server.domain}${server.port != null ? ':${server.port}' : ""}${server.path ?? ""}/control$urlPath";
|
||||
}
|
||||
|
||||
class HttpRequestClient {
|
||||
static Future<HttpResponse> get({
|
||||
required String urlPath,
|
||||
required Server server,
|
||||
int timeout = 10,
|
||||
}) async{
|
||||
final String connectionString = getConnectionString(server: server, urlPath: urlPath);
|
||||
try {
|
||||
HttpClient httpClient = HttpClient();
|
||||
HttpClientRequest request = await httpClient.getUrl(Uri.parse(connectionString));
|
||||
if (server.authToken != null) {
|
||||
request.headers.set('Authorization', 'Basic ${server.authToken}');
|
||||
}
|
||||
HttpClientResponse response = await request.close().timeout(
|
||||
Duration(seconds: timeout)
|
||||
);
|
||||
String reply = await response.transform(utf8.decoder).join();
|
||||
httpClient.close();
|
||||
return HttpResponse(
|
||||
successful: response.statusCode >= 400 ? false : true,
|
||||
body: reply,
|
||||
statusCode: response.statusCode
|
||||
);
|
||||
} on SocketException {
|
||||
return const HttpResponse(
|
||||
successful: false,
|
||||
body: null,
|
||||
statusCode: null,
|
||||
exception: ExceptionType.socket
|
||||
);
|
||||
} on TimeoutException {
|
||||
return const HttpResponse(
|
||||
successful: false,
|
||||
body: null,
|
||||
statusCode: null,
|
||||
exception: ExceptionType.timeout
|
||||
);
|
||||
} on HandshakeException {
|
||||
return const HttpResponse(
|
||||
successful: false,
|
||||
body: null,
|
||||
statusCode: null,
|
||||
exception: ExceptionType.handshake
|
||||
);
|
||||
} on HttpException {
|
||||
return const HttpResponse(
|
||||
successful: false,
|
||||
body: null,
|
||||
statusCode: null,
|
||||
exception: ExceptionType.http
|
||||
);
|
||||
} catch (e) {
|
||||
return const HttpResponse(
|
||||
successful: false,
|
||||
body: null,
|
||||
statusCode: null,
|
||||
exception: ExceptionType.unknown
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<HttpResponse> post({
|
||||
required String urlPath,
|
||||
required Server server,
|
||||
dynamic body,
|
||||
int timeout = 10,
|
||||
}) async{
|
||||
final String connectionString = getConnectionString(server: server, urlPath: urlPath);
|
||||
try {
|
||||
HttpClient httpClient = HttpClient();
|
||||
HttpClientRequest request = await httpClient.postUrl(Uri.parse(connectionString));
|
||||
if (server.authToken != null) {
|
||||
request.headers.set('Authorization', 'Basic ${server.authToken}');
|
||||
}
|
||||
request.headers.set('content-type', 'application/json');
|
||||
request.add(utf8.encode(json.encode(body)));
|
||||
HttpClientResponse response = await request.close().timeout(
|
||||
Duration(seconds: timeout)
|
||||
);
|
||||
String reply = await response.transform(utf8.decoder).join();
|
||||
httpClient.close();
|
||||
return HttpResponse(
|
||||
successful: response.statusCode >= 400 ? false : true,
|
||||
body: reply,
|
||||
statusCode: response.statusCode
|
||||
);
|
||||
} on SocketException {
|
||||
return const HttpResponse(
|
||||
successful: false,
|
||||
body: null,
|
||||
statusCode: null,
|
||||
exception: ExceptionType.socket
|
||||
);
|
||||
} on TimeoutException {
|
||||
return const HttpResponse(
|
||||
successful: false,
|
||||
body: null,
|
||||
statusCode: null,
|
||||
exception: ExceptionType.timeout
|
||||
);
|
||||
} on HttpException {
|
||||
return const HttpResponse(
|
||||
successful: false,
|
||||
body: null,
|
||||
statusCode: null,
|
||||
exception: ExceptionType.http
|
||||
);
|
||||
} on HandshakeException {
|
||||
return const HttpResponse(
|
||||
successful: false,
|
||||
body: null,
|
||||
statusCode: null,
|
||||
exception: ExceptionType.handshake
|
||||
);
|
||||
} catch (e) {
|
||||
return const HttpResponse(
|
||||
successful: false,
|
||||
body: null,
|
||||
statusCode: null,
|
||||
exception: ExceptionType.unknown
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<HttpResponse> put({
|
||||
required String urlPath,
|
||||
required Server server,
|
||||
dynamic body,
|
||||
int timeout = 10,
|
||||
}) async{
|
||||
final String connectionString = getConnectionString(server: server, urlPath: urlPath);
|
||||
try {
|
||||
HttpClient httpClient = HttpClient();
|
||||
HttpClientRequest request = await httpClient.putUrl(Uri.parse(connectionString));
|
||||
if (server.authToken != null) {
|
||||
request.headers.set('Authorization', 'Basic ${server.authToken}');
|
||||
}
|
||||
request.headers.set('content-type', 'application/json');
|
||||
request.add(utf8.encode(json.encode(body)));
|
||||
HttpClientResponse response = await request.close().timeout(
|
||||
Duration(seconds: timeout)
|
||||
);
|
||||
String reply = await response.transform(utf8.decoder).join();
|
||||
httpClient.close();
|
||||
return HttpResponse(
|
||||
successful: response.statusCode >= 400 ? false : true,
|
||||
body: reply,
|
||||
statusCode: response.statusCode
|
||||
);
|
||||
} on SocketException {
|
||||
return const HttpResponse(
|
||||
successful: false,
|
||||
body: null,
|
||||
statusCode: null,
|
||||
exception: ExceptionType.socket
|
||||
);
|
||||
} on TimeoutException {
|
||||
return const HttpResponse(
|
||||
successful: false,
|
||||
body: null,
|
||||
statusCode: null,
|
||||
exception: ExceptionType.timeout
|
||||
);
|
||||
} on HttpException {
|
||||
return const HttpResponse(
|
||||
successful: false,
|
||||
body: null,
|
||||
statusCode: null,
|
||||
exception: ExceptionType.http
|
||||
);
|
||||
} on HandshakeException {
|
||||
return const HttpResponse(
|
||||
successful: false,
|
||||
body: null,
|
||||
statusCode: null,
|
||||
exception: ExceptionType.handshake
|
||||
);
|
||||
} catch (e) {
|
||||
return const HttpResponse(
|
||||
successful: false,
|
||||
body: null,
|
||||
statusCode: null,
|
||||
exception: ExceptionType.unknown
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +1,14 @@
|
|||
import 'package:adguard_home_manager/config/globals.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/process_dialog.dart';
|
||||
|
||||
class ProcessModal {
|
||||
late BuildContext context;
|
||||
|
||||
ProcessModal({
|
||||
required this.context
|
||||
});
|
||||
|
||||
void open(String message) async {
|
||||
await Future.delayed(const Duration(seconds: 0), () => {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (c) {
|
||||
context = c;
|
||||
context: globalNavigatorKey.currentContext!,
|
||||
builder: (ctx) {
|
||||
return ProcessDialog(
|
||||
message: message,
|
||||
);
|
||||
|
@ -26,6 +20,6 @@ class ProcessModal {
|
|||
}
|
||||
|
||||
void close() {
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(globalNavigatorKey.currentContext!);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/filters/filters.dart';
|
||||
import 'package:adguard_home_manager/screens/logs/logs.dart';
|
||||
import 'package:adguard_home_manager/screens/connect/connect.dart';
|
||||
import 'package:adguard_home_manager/screens/home/home.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/clients.dart';
|
||||
import 'package:adguard_home_manager/screens/connect/connect.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/filters.dart';
|
||||
import 'package:adguard_home_manager/screens/home/home.dart';
|
||||
import 'package:adguard_home_manager/screens/logs/logs.dart';
|
||||
import 'package:adguard_home_manager/screens/settings/settings.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/app_screen.dart';
|
||||
|
@ -13,12 +13,12 @@ List<AppScreen> screensSelectServer = [
|
|||
const AppScreen(
|
||||
name: "connect",
|
||||
icon: Icons.link_rounded,
|
||||
body: Connect(),
|
||||
child: Connect()
|
||||
),
|
||||
const AppScreen(
|
||||
name: "settings",
|
||||
icon: Icons.settings_rounded,
|
||||
body: Settings()
|
||||
child: Settings()
|
||||
)
|
||||
];
|
||||
|
||||
|
@ -26,26 +26,26 @@ List<AppScreen> screensServerConnected = [
|
|||
const AppScreen(
|
||||
name: "home",
|
||||
icon: Icons.home_rounded,
|
||||
body: Home(),
|
||||
child: Home()
|
||||
),
|
||||
const AppScreen(
|
||||
name: "clients",
|
||||
icon: Icons.devices,
|
||||
body: Clients()
|
||||
child: Clients()
|
||||
),
|
||||
const AppScreen(
|
||||
name: "logs",
|
||||
icon: Icons.list_alt_rounded,
|
||||
body: Logs(),
|
||||
child: Logs()
|
||||
),
|
||||
const AppScreen(
|
||||
name: "filters",
|
||||
icon: Icons.shield_rounded,
|
||||
body: Filters(),
|
||||
child: Filters()
|
||||
),
|
||||
const AppScreen(
|
||||
name: "settings",
|
||||
icon: Icons.settings_rounded,
|
||||
body: Settings()
|
||||
child: Settings()
|
||||
)
|
||||
];
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||
final GlobalKey<NavigatorState> globalNavigatorKey = GlobalKey<NavigatorState>();
|
|
@ -5,7 +5,9 @@ import 'package:adguard_home_manager/constants/enums.dart';
|
|||
final List<HomeTopItems> homeTopItemsDefaultOrder = [
|
||||
HomeTopItems.queriedDomains,
|
||||
HomeTopItems.blockedDomains,
|
||||
HomeTopItems.recurrentClients
|
||||
HomeTopItems.recurrentClients,
|
||||
HomeTopItems.topUpstreams,
|
||||
HomeTopItems.avgUpstreamResponseTime
|
||||
];
|
||||
|
||||
final String homeTopItemsDefaultOrderString = jsonEncode(
|
||||
|
|
4
lib/config/minimum_server_version.dart
Normal file
4
lib/config/minimum_server_version.dart
Normal file
|
@ -0,0 +1,4 @@
|
|||
class MinimumServerVersion {
|
||||
static const String stable = "v0.107.28";
|
||||
static const String beta = "v0.108.0-b.33";
|
||||
}
|
1
lib/config/sizes.dart
Normal file
1
lib/config/sizes.dart
Normal file
|
@ -0,0 +1 @@
|
|||
const double desktopBreakpoint = 1000;
|
|
@ -15,7 +15,6 @@ ThemeData lightTheme(ColorScheme? dynamicColorScheme) => ThemeData(
|
|||
textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(117, 117, 117, 1),
|
||||
iconColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(117, 117, 117, 1),
|
||||
),
|
||||
androidOverscrollIndicator: AndroidOverscrollIndicator.stretch,
|
||||
);
|
||||
|
||||
ThemeData darkTheme(ColorScheme? dynamicColorScheme) => ThemeData(
|
||||
|
@ -34,7 +33,6 @@ ThemeData darkTheme(ColorScheme? dynamicColorScheme) => ThemeData(
|
|||
textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(187, 187, 187, 1),
|
||||
iconColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(187, 187, 187, 1),
|
||||
),
|
||||
androidOverscrollIndicator: AndroidOverscrollIndicator.stretch,
|
||||
);
|
||||
|
||||
ThemeData lightThemeOldVersions(MaterialColor primaryColor) => ThemeData(
|
||||
|
@ -53,7 +51,6 @@ ThemeData lightThemeOldVersions(MaterialColor primaryColor) => ThemeData(
|
|||
iconColor: Color.fromRGBO(117, 117, 117, 1),
|
||||
),
|
||||
brightness: Brightness.light,
|
||||
androidOverscrollIndicator: AndroidOverscrollIndicator.stretch
|
||||
);
|
||||
|
||||
ThemeData darkThemeOldVersions(MaterialColor primaryColor) => ThemeData(
|
||||
|
@ -75,5 +72,4 @@ ThemeData darkThemeOldVersions(MaterialColor primaryColor) => ThemeData(
|
|||
iconColor: Color.fromRGBO(187, 187, 187, 1),
|
||||
),
|
||||
brightness: Brightness.dark,
|
||||
androidOverscrollIndicator: AndroidOverscrollIndicator.stretch
|
||||
);
|
|
@ -1,2 +1,2 @@
|
|||
enum LoadStatus { loading, loaded, error }
|
||||
enum HomeTopItems { queriedDomains, blockedDomains, recurrentClients }
|
||||
enum HomeTopItems { queriedDomains, blockedDomains, recurrentClients, topUpstreams, avgUpstreamResponseTime }
|
31
lib/constants/routes_names.dart
Normal file
31
lib/constants/routes_names.dart
Normal file
|
@ -0,0 +1,31 @@
|
|||
class RoutesNames {
|
||||
static const String connect = "/connect";
|
||||
|
||||
static const String home = "/home";
|
||||
static const String queriedDomains = "/home/queried-domains";
|
||||
static const String blockedDomains = "/home/blocked-domains";
|
||||
static const String recurrentClients = "/home/recurrent-clients";
|
||||
|
||||
static const String clients = "/clients";
|
||||
static const String clientsList = "/clients/list";
|
||||
static const String clientPlaceholder = "/clients/list/placeholder";
|
||||
static const String client = "/clients/list:id";
|
||||
|
||||
static const String logs = "/logs";
|
||||
|
||||
static const String filters = "/filters";
|
||||
|
||||
static const String settings = "/settings";
|
||||
static const String safeSearch = "/settings/safe-search";
|
||||
static const String accessSettings = "/settings/access-settigs";
|
||||
static const String dhcpSettings = "/settings/dhcp-settings";
|
||||
static const String dnsSettings = "/settings/dns-settings";
|
||||
static const String encryptionSettings = "/settings/encryption-settings";
|
||||
static const String dnsRewrites = "/settings/dns-rewrites";
|
||||
static const String serverUpdates = "/settings/server-updates";
|
||||
static const String serverInfo = "/settings/server-info";
|
||||
static const String customization = "/settings/customization";
|
||||
static const String servers = "/settings/servers";
|
||||
static const String generalSettings = "/settings/general-settings";
|
||||
static const String advancedSettings = "/settings/advanced-settings";
|
||||
}
|
|
@ -3,6 +3,7 @@ class Urls {
|
|||
static const String gitHub = "https://github.com/JGeek00/adguard-home-manager";
|
||||
static const String customRuleDocs = "https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters";
|
||||
static const String getReleasesGitHub = "https://api.github.com/repos/JGeek00/adguard-home-manager/releases";
|
||||
static const String getLatestReleaseGitHub = "https://api.github.com/repos/JGeek00/adguard-home-manager/releases/latest";
|
||||
static const String adGuardHomeReleasesTags = "https://api.github.com/repos/AdGuardTeam/AdGuardHome/releases/tags";
|
||||
static const String googleSearchUrl = "https://www.google.com/search";
|
||||
static const String connectionInstructions = "https://github.com/JGeek00/adguard-home-manager/wiki/Create-a-connection";
|
||||
|
|
|
@ -3,8 +3,8 @@ import 'dart:io';
|
|||
import 'package:store_checker/store_checker.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/compare_versions.dart';
|
||||
import 'package:adguard_home_manager/services/external_requests.dart';
|
||||
import 'package:adguard_home_manager/models/github_release.dart';
|
||||
import 'package:adguard_home_manager/services/http_requests.dart';
|
||||
|
||||
Future<GitHubRelease?> checkAppUpdates({
|
||||
required String currentBuildNumber,
|
||||
|
@ -12,21 +12,27 @@ Future<GitHubRelease?> checkAppUpdates({
|
|||
required Source installationSource,
|
||||
required bool isBeta
|
||||
}) async {
|
||||
final result = await checkAppUpdatesGitHub();
|
||||
var result = isBeta
|
||||
? await ExternalRequests.getReleasesGitHub()
|
||||
: await ExternalRequests.getReleaseData();
|
||||
|
||||
if (result.successful == true) {
|
||||
late GitHubRelease gitHubRelease;
|
||||
if (isBeta) {
|
||||
gitHubRelease = (result.content as List<GitHubRelease>).firstWhere((r) => r.prerelease == true);
|
||||
}
|
||||
else {
|
||||
gitHubRelease = result.content as GitHubRelease;
|
||||
}
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
final update = gitHubUpdateExists(
|
||||
currentBuildNumber: currentBuildNumber,
|
||||
gitHubReleases: result['body'],
|
||||
gitHubRelease: gitHubRelease,
|
||||
isBeta: isBeta
|
||||
);
|
||||
|
||||
print(update);
|
||||
if (update == true) {
|
||||
final release = isBeta == true
|
||||
? result['body'].firstWhere((release) => release.prerelease == true)
|
||||
: result['body'].firstWhere((release) => release.prerelease == false);
|
||||
|
||||
setUpdateAvailable(release);
|
||||
setUpdateAvailable(gitHubRelease);
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
if (
|
||||
|
@ -34,7 +40,7 @@ Future<GitHubRelease?> checkAppUpdates({
|
|||
installationSource == Source.IS_INSTALLED_FROM_PLAY_PACKAGE_INSTALLER ||
|
||||
installationSource == Source.UNKNOWN
|
||||
) {
|
||||
return release;
|
||||
return gitHubRelease;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
|
@ -44,7 +50,7 @@ Future<GitHubRelease?> checkAppUpdates({
|
|||
return null;
|
||||
}
|
||||
else {
|
||||
return release;
|
||||
return gitHubRelease;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -4,27 +4,20 @@ import 'package:flutter/material.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/services/api_client.dart';
|
||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||
import 'package:adguard_home_manager/models/server.dart';
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
|
||||
Future<bool> clearDnsCache(BuildContext context, Server server) async {
|
||||
Future<ApiResponse> clearDnsCache(BuildContext context, Server server) async {
|
||||
final serversProvider = Provider.of<ServersProvider>(context, listen: false);
|
||||
|
||||
final ProcessModal processModal = ProcessModal(context: context);
|
||||
final ProcessModal processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.clearingDnsCache);
|
||||
|
||||
final result = await serversProvider.apiClient!.resetDnsCache();
|
||||
final result = await serversProvider.apiClient2!.resetDnsCache();
|
||||
|
||||
processModal.close();
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context, listen: false);
|
||||
appConfigProvider.addLog(result['log']);
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -151,15 +151,11 @@ bool serverVersionIsAhead({
|
|||
|
||||
bool gitHubUpdateExists({
|
||||
required String currentBuildNumber,
|
||||
required List<GitHubRelease> gitHubReleases,
|
||||
required GitHubRelease gitHubRelease,
|
||||
required bool isBeta
|
||||
}) {
|
||||
final release = isBeta == true
|
||||
? gitHubReleases.firstWhere((release) => release.prerelease == true)
|
||||
: gitHubReleases.firstWhere((release) => release.prerelease == false);
|
||||
|
||||
final versionNumberRegex = RegExp(r'\(\d+\)');
|
||||
final releaseNumberExtractedMatches = versionNumberRegex.allMatches(release.tagName);
|
||||
final releaseNumberExtractedMatches = versionNumberRegex.allMatches(gitHubRelease.tagName);
|
||||
|
||||
if (releaseNumberExtractedMatches.isNotEmpty) {
|
||||
final releaseNumberExtracted = releaseNumberExtractedMatches.first.group(0);
|
||||
|
@ -181,12 +177,12 @@ bool gitHubUpdateExists({
|
|||
}
|
||||
}
|
||||
else {
|
||||
Sentry.captureMessage("Invalid release number. Tagname: ${release.tagName}");
|
||||
Sentry.captureMessage("Invalid release number. Tagname: ${gitHubRelease.tagName}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Sentry.captureMessage("No matches. ${release.tagName}");
|
||||
Sentry.captureMessage("No matches. ${gitHubRelease.tagName}");
|
||||
return false;
|
||||
}
|
||||
}
|
5
lib/functions/desktop_mode.dart
Normal file
5
lib/functions/desktop_mode.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
import 'package:adguard_home_manager/config/sizes.dart';
|
||||
|
||||
bool isDesktop(double width) {
|
||||
return width > desktopBreakpoint;
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
"connect": "Connect",
|
||||
"servers": "Servers",
|
||||
"createConnection": "Create connection",
|
||||
"editConnection": "Edit connection",
|
||||
"name": "Name",
|
||||
"ipDomain": "IP address or domain",
|
||||
"path": "Path",
|
||||
|
@ -56,10 +57,10 @@
|
|||
"serverStatusNotRefreshed": "Server status could not be refreshed",
|
||||
"loadingStatus": "Loading status...",
|
||||
"errorLoadServerStatus": "Server status could not be loaded",
|
||||
"topQueriedDomains": "Top queried domains",
|
||||
"topQueriedDomains": "Queried domains",
|
||||
"viewMore": "View more",
|
||||
"topClients": "Top clients",
|
||||
"topBlockedDomains": "Top blocked domains",
|
||||
"topClients": "Clients",
|
||||
"topBlockedDomains": "Blocked domains",
|
||||
"appSettings": "App settings",
|
||||
"theme": "Theme",
|
||||
"light": "Light",
|
||||
|
@ -170,7 +171,7 @@
|
|||
"dnsQueries": "DNS queries",
|
||||
"average": "Average",
|
||||
"blockedFilters": "Blocked by filters",
|
||||
"malwarePhisingBlocked": "Blocked malware/phising",
|
||||
"malwarePhishingBlocked": "Blocked malware/phishing",
|
||||
"blockedAdultWebsites": "Blocked adult websites",
|
||||
"generalSettings": "General settings",
|
||||
"generalSettingsDescription": "Various different settings",
|
||||
|
@ -316,6 +317,7 @@
|
|||
"deletingRule": "Deleting rule...",
|
||||
"enablingList": "Enabling list...",
|
||||
"disablingList": "Disabling list...",
|
||||
"savingList": "Saving list...",
|
||||
"disableFiltering": "Disable filtering",
|
||||
"enablingFiltering": "Enabling filtering...",
|
||||
"disablingFiltering": "Disabling filtering...",
|
||||
|
@ -649,7 +651,7 @@
|
|||
"october": "October",
|
||||
"november": "November",
|
||||
"december": "December",
|
||||
"malwarePhising": "Malware/phising",
|
||||
"malwarePhishing": "Malware/phishing",
|
||||
"queries": "Queries",
|
||||
"adultSites": "Adult sites",
|
||||
"quickFilters": "Quick filters",
|
||||
|
@ -660,5 +662,69 @@
|
|||
"topItemsOrderDescription": "Order the home screen top items lists",
|
||||
"topItemsReorderInfo": "Hold and swipe an item to reorder it.",
|
||||
"discardChanges": "Discard changes",
|
||||
"discardChangesDescription": "Are you sure you want to discard the changes?"
|
||||
"discardChangesDescription": "Are you sure you want to discard the changes?",
|
||||
"others": "Others",
|
||||
"showChart": "Show chart",
|
||||
"hideChart": "Hide chart",
|
||||
"showTopItemsChart": "Show top items chart",
|
||||
"showTopItemsChartDescription": "Shows by default the ring chart on the top items sections. Only affects to the mobile view.",
|
||||
"openMenu": "Open menu",
|
||||
"closeMenu": "Close menu",
|
||||
"openListUrl": "Open list URL",
|
||||
"selectionMode": "Selection mode",
|
||||
"enableDisableSelected": "Enable or disable selected items",
|
||||
"deleteSelected": "Delete selected items",
|
||||
"deleteSelectedLists": "Delete selected lists",
|
||||
"allSelectedListsDeletedSuccessfully": "All selected lists have been deleted successfully.",
|
||||
"deletionResult": "Deletion result",
|
||||
"deletingLists": "Deleting lists...",
|
||||
"failedElements": "Failed elements",
|
||||
"processingLists": "Processing lists...",
|
||||
"enableDisableResult": "Enable or disable result",
|
||||
"selectedListsEnabledDisabledSuccessfully": "All selected lists have been enabled or disabled successfully",
|
||||
"sslWarning": "If you are using an HTTPS connection with a self signed certificate, make sure to enable \"Don't check SSL certificate\" at Settings > Advanced settings.",
|
||||
"unsupportedServerVersion": "Unsupported server version",
|
||||
"unsupportedServerVersionMessage": "Your AdGuard Home server version is too old and is not supported by AdGuard Home Manager. You will need to upgrade your AdGuard Home server to a newer version to use this application.",
|
||||
"yourVersion": "Your version: {version}",
|
||||
"minimumRequiredVersion": "Minimum required version: {version}",
|
||||
"topUpstreams": "Top upstreams",
|
||||
"averageUpstreamResponseTime": "Average upstream response time",
|
||||
"dhcpNotAvailable": "The DHCP server is not available.",
|
||||
"osServerInstalledIncompatible": "The OS where the server is installed is not compatible with this feature.",
|
||||
"resetSettings": "Reset settings",
|
||||
"resetEncryptionSettingsDescription": "Are you sure you want to reset to default values the encryption settings?",
|
||||
"resettingConfig": "Resetting configuration...",
|
||||
"configurationResetSuccessfully": "Configuration resetted successfully",
|
||||
"configurationResetError": "The configuration couldn't be resetted",
|
||||
"testUpstreamDnsServers": "Test upstream DNS servers",
|
||||
"errorTestUpstreamDns": "Error when testing upstream DNS servers.",
|
||||
"useCustomIpEdns": "Use custom IP for EDNS",
|
||||
"useCustomIpEdnsDescription": "Allow to use custom IP for EDNS",
|
||||
"sortingOptions": "Sorting options",
|
||||
"fromHighestToLowest": "From highest to lowest",
|
||||
"fromLowestToHighest": "From lowest to highest",
|
||||
"queryLogsAndStatistics": "Query logs and statistics",
|
||||
"ignoreClientQueryLog": "Ignore this client in query log",
|
||||
"ignoreClientStatistics": "Ignore this client in statistics",
|
||||
"savingChanges": "Saving changes...",
|
||||
"fallbackDnsServers": "Fallback DNS servers",
|
||||
"fallbackDnsServersDescription": "Configure fallback DNS servers",
|
||||
"fallbackDnsServersInfo": "List of fallback DNS servers used when upstream DNS servers are not responding. The syntax is the same as in the main upstreams field above.",
|
||||
"noFallbackDnsAdded": "No fallback DNS servers added.",
|
||||
"blockedResponseTtl": "Blocked response TTL",
|
||||
"blockedResponseTtlDescription": "Specifies for how many seconds the clients should cache a filtered response",
|
||||
"invalidValue": "Invalid value",
|
||||
"noDataChart": "There's no data to display this chart.",
|
||||
"noData": "No data",
|
||||
"unblockClient": "Unblock client",
|
||||
"blockingClient": "Blocking client...",
|
||||
"unblockingClient": "Unblocking client...",
|
||||
"upstreamDnsCacheConfiguration": "Configuración de la caché DNS upstream",
|
||||
"enableDnsCachingClient": "Enable DNS caching for this client",
|
||||
"dnsCacheSize": "DNS cache size",
|
||||
"nameInvalid": "Name is required",
|
||||
"oneIdentifierRequired": "At least one identifier is required",
|
||||
"dnsCacheNumber": "DNS cache size must be a number",
|
||||
"errors": "Errors",
|
||||
"redirectHttpsWarning": "If you have enabled \"Redirect to HTTPS automatically\" on your AdGuard Home server, you must select an HTTPS connection and use the HTTPS port of your server."
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
"connect": "Conectar",
|
||||
"servers": "Servidores",
|
||||
"createConnection": "Crear conexión",
|
||||
"editConnection": "Editar conexión",
|
||||
"name": "Nombre",
|
||||
"ipDomain": "Dirección IP o dominio",
|
||||
"path": "Ruta",
|
||||
|
@ -56,10 +57,10 @@
|
|||
"serverStatusNotRefreshed": "No se ha podido actualizar el estado del servidor",
|
||||
"loadingStatus": "Cargando estado...",
|
||||
"errorLoadServerStatus": "Error al cargar el estado",
|
||||
"topQueriedDomains": "Top dominios solicitados",
|
||||
"topQueriedDomains": "Dominios solicitados",
|
||||
"viewMore": "Ver más",
|
||||
"topClients": "Top clientes recurrentes",
|
||||
"topBlockedDomains": "Top dominios bloqueados",
|
||||
"topClients": "Clientes recurrentes",
|
||||
"topBlockedDomains": "Dominios bloqueados",
|
||||
"appSettings": "Ajustes de la app",
|
||||
"theme": "Tema",
|
||||
"light": "Claro",
|
||||
|
@ -170,7 +171,7 @@
|
|||
"dnsQueries": "Consultas DNS",
|
||||
"average": "Promedio",
|
||||
"blockedFilters": "Bloqueado por filtros",
|
||||
"malwarePhisingBlocked": "Malware/phising bloqueado",
|
||||
"malwarePhishingBlocked": "Malware/phising bloqueado",
|
||||
"blockedAdultWebsites": "Sitios para adultos bloqueados",
|
||||
"generalSettings": "Ajustes generales",
|
||||
"generalSettingsDescription": "Varios ajustes generales",
|
||||
|
@ -316,6 +317,7 @@
|
|||
"deletingRule": "Eliminando regla...",
|
||||
"enablingList": "Habilitando lista...",
|
||||
"disablingList": "Deshabilitando lista...",
|
||||
"savingList": "Guardando lista...",
|
||||
"disableFiltering": "Deshabilitar filtrado",
|
||||
"enablingFiltering": "Habilitando filtrado...",
|
||||
"disablingFiltering": "Deshabilitando filtrado...",
|
||||
|
@ -649,7 +651,7 @@
|
|||
"october": "Octubre",
|
||||
"november": "Noviembre",
|
||||
"december": "Diciembre",
|
||||
"malwarePhising": "Malware/phising",
|
||||
"malwarePhishing": "Malware/phising",
|
||||
"queries": "Peticiones",
|
||||
"adultSites": "Sitios de adultos",
|
||||
"quickFilters": "Filtros rápidos",
|
||||
|
@ -660,5 +662,69 @@
|
|||
"topItemsOrderDescription": "Ordena las listas de top de elementos en la pantalla de inicio",
|
||||
"topItemsReorderInfo": "Mantén presionado y desliza un elemento para reordenarlo.",
|
||||
"discardChanges": "Descartar cambios",
|
||||
"discardChangesDescription": "¿Estás seguro de que deseas descartar los cambios realizados?"
|
||||
"discardChangesDescription": "¿Estás seguro de que deseas descartar los cambios realizados?",
|
||||
"others": "Otros",
|
||||
"showChart": "Mostrar gráfico",
|
||||
"hideChart": "Ocultar gráfico",
|
||||
"showTopItemsChart": "Mostrar gráfico en top de items",
|
||||
"showTopItemsChartDescription": "Muestra por defecto el gráfico de anillo en las secciones de top de items. Sólo afecta a la vista móvil.",
|
||||
"openMenu": "Abrir menú",
|
||||
"closeMenu": "Cerrar menú",
|
||||
"openListUrl": "Abrir URL de lista",
|
||||
"selectionMode": "Modo de selección",
|
||||
"enableDisableSelected": "Activar o desactivar elementos seleccionados",
|
||||
"deleteSelected": "Eliminar elementos seleccionados",
|
||||
"deleteSelectedLists": "Eliminar listas seleccionadas",
|
||||
"allSelectedListsDeletedSuccessfully": "Todas las listas seleccionadas han sido eliminadas correctamente.",
|
||||
"deletionResult": "Resultado de eliminación",
|
||||
"deletingLists": "Eliminando listas...",
|
||||
"failedElements": "Elementos fallidos",
|
||||
"processingLists": "Procesando listas...",
|
||||
"enableDisableResult": "Resultado de activar o desactivar",
|
||||
"selectedListsEnabledDisabledSuccessfully": "Todas las listas seleccionadas se han activado o desactivado correctamente.",
|
||||
"sslWarning": "Si estás usando una conexión HTTPS con un certificado autofirmado, asegúrate de activar \"No comprobar el certificado SSL\" en Ajustes > Ajustes avanzados.",
|
||||
"unsupportedServerVersion": "Versión del servidor no soportada",
|
||||
"unsupportedServerVersionMessage": "La versión de tu servidor AdGuard Home es demasiado antigua y no está soportada por AdGuard Home Manager. Necesitarás actualizar tu servidor AdGuard Home a una versión más actual para utilizar esta aplicación.",
|
||||
"yourVersion": "Tu versión: {version}",
|
||||
"minimumRequiredVersion": "Versión mínima requerida: {version}",
|
||||
"topUpstreams": "DNS de subida más frecuentes",
|
||||
"averageUpstreamResponseTime": "Tiempo promedio de respuesta upstream",
|
||||
"dhcpNotAvailable": "El servidor DHCP no está disponible.",
|
||||
"osServerInstalledIncompatible": "El SO donde el servidor está instalado no es compatible con esta característica.",
|
||||
"resetSettings": "Resetear configuración",
|
||||
"resetEncryptionSettingsDescription": "Estás seguro que deseas restaurar a valores por defecto la configuración de encriptación?",
|
||||
"resettingConfig": "Reseteando configuración...",
|
||||
"configurationResetSuccessfully": "Configuración reseteada correctamente",
|
||||
"configurationResetError": "La configuración no ha podido ser reseteada",
|
||||
"testUpstreamDnsServers": "Probar servidores DNS de subida",
|
||||
"errorTestUpstreamDns": "Error al probar los servidores DNS de subida.",
|
||||
"useCustomIpEdns": "Usar IP personalizada para EDNS",
|
||||
"useCustomIpEdnsDescription": "Permitir usar IP personalizada para EDNS",
|
||||
"sortingOptions": "Opciones de ordenación",
|
||||
"fromHighestToLowest": "De mayor a menor",
|
||||
"fromLowestToHighest": "De menor a mayor",
|
||||
"queryLogsAndStatistics": "Registro de consultas y estadísticas",
|
||||
"ignoreClientQueryLog": "Ignorar este cliente en el registro de consultas",
|
||||
"ignoreClientStatistics": "Ignorar este cliente en las estadísticas",
|
||||
"savingChanges": "Guardando cambios...",
|
||||
"fallbackDnsServers": "Servidores DNS alternativos",
|
||||
"fallbackDnsServersDescription": "Configura los servidores DNS alternativos",
|
||||
"fallbackDnsServersInfo": "Lista de servidores DNS alternativos utilizados cuando los servidores DNS de subida no responden. La sintaxis es la misma que en el campo de los principales DNS de subida anterior.",
|
||||
"noFallbackDnsAdded": "No hay servidores DNS alternativos añadidos.",
|
||||
"blockedResponseTtl": "Respuesta TTL bloqueada",
|
||||
"blockedResponseTtlDescription": "Especifica durante cuántos segundos los clientes deben almacenar en cache una respuesta filtrada",
|
||||
"invalidValue": "Valor no válido",
|
||||
"noDataChart": "No hay datos para mostrar este gráfico.",
|
||||
"noData": "No hay datos",
|
||||
"unblockClient": "Desbloquear cliente",
|
||||
"blockingClient": "Bloqueando cliente...",
|
||||
"unblockingClient": "Desbloqueando cliente...",
|
||||
"upstreamDnsCacheConfiguration": "Configuración de la caché DNS upstream",
|
||||
"enableDnsCachingClient": "Habilitar caché de DNS para este cliente",
|
||||
"dnsCacheSize": "Tamaño de caché de DNS",
|
||||
"nameInvalid": "Se requiere un nombre",
|
||||
"oneIdentifierRequired": "Se require al menos un identificador",
|
||||
"dnsCacheNumber": "El tamaño de caché de DNS debe ser un número",
|
||||
"errors": "Errores",
|
||||
"redirectHttpsWarning": "Si tienes activado \"Redireccionar a HTTPS automáticamente\" en tu servidor AdGuard Home, debes seleccionar una conexión HTTPS y utilizar el puerto de HTTPS de tu servidor."
|
||||
}
|
|
@ -170,7 +170,7 @@
|
|||
"dnsQueries": "Zapytania DNS",
|
||||
"average": "Średnia",
|
||||
"blockedFilters": "Zablokowane przez filtry",
|
||||
"malwarePhisingBlocked": "Zablokowane złośliwe oprogramowanie/phishing",
|
||||
"malwarePhishingBlocked": "Zablokowane złośliwe oprogramowanie/phishing",
|
||||
"blockedAdultWebsites": "Zablokowane witryny dla dorosłych",
|
||||
"generalSettings": "Ustawienia główne",
|
||||
"generalSettingsDescription": "Różne ustawienia",
|
||||
|
@ -649,7 +649,7 @@
|
|||
"october": "Październik",
|
||||
"november": "Listopad",
|
||||
"december": "Grudzień",
|
||||
"malwarePhising": "Złośliwe oprogramowanie / wyłudzanie informacji",
|
||||
"malwarePhishing": "Złośliwe oprogramowanie / wyłudzanie informacji",
|
||||
"queries": "Zapytania",
|
||||
"adultSites": "Strony dla dorosłych",
|
||||
"quickFilters": "Szybkie filtry",
|
||||
|
|
692
lib/l10n/app_tr.arb
Normal file
692
lib/l10n/app_tr.arb
Normal file
|
@ -0,0 +1,692 @@
|
|||
{
|
||||
"home": "Anasayfa",
|
||||
"settings": "Ayarlar",
|
||||
"connect": "Bağlan",
|
||||
"servers": "Sunucular",
|
||||
"createConnection": "Bağlantı oluştur",
|
||||
"editConnection": "Bağlantıyı düzenle",
|
||||
"name": "Ad",
|
||||
"ipDomain": "IP adresi veya alan adı",
|
||||
"path": "Dosya Yolu",
|
||||
"port": "Bağlantı noktası",
|
||||
"username": "Kullanıcı adı",
|
||||
"password": "Şifre",
|
||||
"defaultServer": "Varsayılan sunucu",
|
||||
"general": "Genel",
|
||||
"connection": "Bağlantı",
|
||||
"authentication": "Kimlik doğrulama",
|
||||
"other": "Diğer",
|
||||
"invalidPort": "Geçersiz bağlantı noktası",
|
||||
"invalidPath": "Geçersiz dosya yolu",
|
||||
"invalidIpDomain": "Geçersiz IP veya alan adı",
|
||||
"ipDomainNotEmpty": "IP veya alan adı boş olamaz",
|
||||
"nameNotEmpty": "Ad boş bırakılamaz",
|
||||
"invalidUsernamePassword": "Geçersiz kullanıcı adı veya şifre",
|
||||
"tooManyAttempts": "Çok fazla deneme yapıldı. Daha sonra tekrar deneyin.",
|
||||
"cantReachServer": "Sunucuya ulaşılamıyor. Bağlantınızı kontrol edin.",
|
||||
"sslError": "SSL hatası. Ayarlar > Gelişmiş ayarlar bölümüne gidin ve SSL doğrulamasını geçersiz kıl seçeneğini etkinleştirin.",
|
||||
"unknownError": "Bilinmeyen hata",
|
||||
"connectionNotCreated": "Bağlantı kurulamadı",
|
||||
"connecting": "Bağlanılıyor...",
|
||||
"connected": "Bağlantı kuruldu",
|
||||
"selectedDisconnected": "Seçildi ancak bağlantı kesildi",
|
||||
"connectionDefaultSuccessfully": "Bağlantı başarıyla varsayılan olarak ayarlandı.",
|
||||
"connectionDefaultFailed": "Bağlantı varsayılan olarak ayarlanamadı.",
|
||||
"noSavedConnections": "Kaydedilmiş bağlantı yok",
|
||||
"cannotConnect": "Sunucuya bağlanılamıyor",
|
||||
"connectionRemoved": "Bağlantı başarıyla kaldırıldı",
|
||||
"connectionCannotBeRemoved": "Bağlantı kaldırılamaz.",
|
||||
"remove": "Kaldır",
|
||||
"removeWarning": "Bu AdGuard Home sunucusuyla olan bağlantıyı kaldırmak istediğinizden emin misiniz?",
|
||||
"cancel": "İptal",
|
||||
"defaultConnection": "Varsayılan bağlantı",
|
||||
"setDefault": "Varsayılan ayarla",
|
||||
"edit": "Düzenle",
|
||||
"delete": "Sil",
|
||||
"save": "Kaydet",
|
||||
"serverStatus": "Sunucu durumu",
|
||||
"connectionNotUpdated": "Bağlantı Güncellenmedi",
|
||||
"ruleFilteringWidget": "Kural filtreleme",
|
||||
"safeBrowsingWidget": "Güvenli gezinti",
|
||||
"parentalFilteringWidget": "Ebeveyn filtreleme",
|
||||
"safeSearchWidget": "Güvenli arama",
|
||||
"ruleFiltering": "Kural filtreleme",
|
||||
"safeBrowsing": "Güvenli gezinti",
|
||||
"parentalFiltering": "Ebeveyn filtreleme",
|
||||
"safeSearch": "Güvenli arama",
|
||||
"serverStatusNotRefreshed": "Sunucu durumu yenilenemedi",
|
||||
"loadingStatus": "Durum yükleniyor...",
|
||||
"errorLoadServerStatus": "Sunucu durumu yüklenemedi",
|
||||
"topQueriedDomains": "En çok sorgulananlar",
|
||||
"viewMore": "Daha fazla göster",
|
||||
"topClients": "Öne çıkan istemciler",
|
||||
"topBlockedDomains": "En çok engellenenler",
|
||||
"appSettings": "Uygulama ayarları",
|
||||
"theme": "Tema",
|
||||
"light": "Aydınlık",
|
||||
"dark": "Karanlık",
|
||||
"systemDefined": "Sistemle uyumlu hale getir",
|
||||
"close": "Kapat",
|
||||
"connectedTo": "Bağlandı:",
|
||||
"selectedServer": "Seçili sunucu:",
|
||||
"noServerSelected": "Seçili sunucu yok",
|
||||
"manageServer": "Sunucuyu yönet",
|
||||
"allProtections": "Tüm korumalar",
|
||||
"userNotEmpty": "Kullanıcı adı boş bırakılamaz",
|
||||
"passwordNotEmpty": "Şifre boş bırakılamaz",
|
||||
"examplePath": "Örnek: /adguard",
|
||||
"helperPath": "Ters proxy kullanıyorsanız",
|
||||
"aboutApp": "Uygulama hakkında",
|
||||
"appVersion": "Uygulama sürümü",
|
||||
"createdBy": "Geliştirici",
|
||||
"clients": "İstemciler",
|
||||
"allowed": "İzin verildi",
|
||||
"blocked": "Engellendi",
|
||||
"noClientsList": "Bu listede hiç istemci yok",
|
||||
"activeClients": "Etkin",
|
||||
"removeClient": "İstemciyi kaldır",
|
||||
"removeClientMessage": "Bu istemciyi listeden çıkarmak istediğinize emin misiniz?",
|
||||
"confirm": "Onayla",
|
||||
"removingClient": "İstemci kaldırılıyor...",
|
||||
"clientNotRemoved": "İstemci listeden çıkarılamadı",
|
||||
"addClient": "İstemci ekle",
|
||||
"list": "Liste",
|
||||
"ipAddress": "IP adresi",
|
||||
"ipNotValid": "IP adresi geçersiz",
|
||||
"clientAddedSuccessfully": "İstemci listeye başarıyla eklendi",
|
||||
"addingClient": "İstemci ekleniyor...",
|
||||
"clientNotAdded": "İstemci listeye eklenemedi",
|
||||
"clientAnotherList": "Bu istemci henüz başka bir listede",
|
||||
"noSavedLogs": "Kayıtlı günlük yok",
|
||||
"logs": "Günlükler",
|
||||
"copyLogsClipboard": "Günlükleri panoya kopyala",
|
||||
"logsCopiedClipboard": "Günlükler panoya kopyalandı",
|
||||
"advancedSettings": "Gelişmiş ayarlar",
|
||||
"dontCheckCertificate": "SSL sertifikasını asla kontrol etme",
|
||||
"dontCheckCertificateDescription": "Sunucunun SSL sertifikası doğrulamasını geçersiz kılar",
|
||||
"advancedSetupDescription": "Gelişmiş seçenekleri yönet",
|
||||
"settingsUpdatedSuccessfully": "Ayarlar başarıyla güncellendi.",
|
||||
"cannotUpdateSettings": "Ayarlar güncellenemiyor.",
|
||||
"restartAppTakeEffect": "Uygulamayı yeniden başlat",
|
||||
"loadingLogs": "Günlükler yükleniyor...",
|
||||
"logsNotLoaded": "Günlüklerin listesi yüklenemedi",
|
||||
"processed": "İşlendi\nListe yok",
|
||||
"processedRow": "İşlendi (Liste yok)",
|
||||
"blockedBlacklist": "Engellendi\nKara Liste",
|
||||
"blockedBlacklistRow": "Engellendi (Kara liste)",
|
||||
"blockedSafeBrowsing": "Engellendi\nGüvenli gezinti",
|
||||
"blockedSafeBrowsingRow": "Engellendi (Güvenli gezinti)",
|
||||
"blockedParental": "Engellendi\nEbeveyn filtreleme",
|
||||
"blockedParentalRow": "Engellendi (Ebeveyn filtreleme)",
|
||||
"blockedInvalid": "Engellendi\nGeçersiz",
|
||||
"blockedInvalidRow": "Engellendi (Geçersiz)",
|
||||
"blockedSafeSearch": "Engellendi\nGüvenli arama",
|
||||
"blockedSafeSearchRow": "Engellendi (Güvenli arama)",
|
||||
"blockedService": "Engellendi\nBelirlenen hizmet",
|
||||
"blockedServiceRow": "Engellendi (Belirlenen hizmet)",
|
||||
"processedWhitelist": "İşlendi\nBeyaz liste",
|
||||
"processedWhitelistRow": "İşlendi (Beyaz liste)",
|
||||
"processedError": "İşlendi\nHata",
|
||||
"processedErrorRow": "İşlendi (Hata)",
|
||||
"rewrite": "Yeniden Yaz",
|
||||
"status": "Durum",
|
||||
"result": "Sonuç",
|
||||
"time": "Zaman",
|
||||
"blocklist": "Engelleme Listesi",
|
||||
"request": "İstek",
|
||||
"domain": "Alan adı",
|
||||
"type": "Tip",
|
||||
"clas": "Sınıf",
|
||||
"response": "Yanıt",
|
||||
"dnsServer": "DNS sunucusu",
|
||||
"elapsedTime": "İşlem süresi",
|
||||
"responseCode": "Yanıt kodu",
|
||||
"client": "İstemci",
|
||||
"deviceIp": "IP adresi",
|
||||
"deviceName": "İstemci adı",
|
||||
"logDetails": "Günlük detayları",
|
||||
"blockingRule": "Engelleme kuralı",
|
||||
"blockDomain": "Alan adını engelle",
|
||||
"couldntGetFilteringStatus": "Filtreleme durumu alınamıyor",
|
||||
"unblockDomain": "Alan adı engelini kaldır",
|
||||
"userFilteringRulesNotUpdated": "Kullanıcı filtreleme kuralları güncellenemedi",
|
||||
"userFilteringRulesUpdated": "Kullanıcı filtreleme kuralları başarıyla güncellendi",
|
||||
"savingUserFilters": "Kullanıcı filtreleri kaydediliyor...",
|
||||
"filters": "Filtreler",
|
||||
"logsOlderThan": "Daha eski günlükler",
|
||||
"responseStatus": "Yanıt durumu",
|
||||
"selectTime": "Zaman seç",
|
||||
"notSelected": "Seçili değil",
|
||||
"resetFilters": "Filtreleri sıfırla",
|
||||
"noLogsDisplay": "Gösterilecek günlük yok",
|
||||
"noLogsThatOld": "Seçilen zaman için kaydedilmiş herhangi bir günlük bulunmuyor olabilir. Daha yakın bir zaman seçmeyi deneyin.",
|
||||
"apply": "Uygula",
|
||||
"selectAll": "Hepsini seç",
|
||||
"unselectAll": "Seçimleri kaldır",
|
||||
"all": "Hepsi",
|
||||
"filtered": "Filtrelenmiş",
|
||||
"checkAppLogs": "Uygulama günlüklerini kontrol edin",
|
||||
"refresh": "Yenile",
|
||||
"search": "Ara",
|
||||
"dnsQueries": "DNS sorguları",
|
||||
"average": "Ortalama",
|
||||
"blockedFilters": "Engellenen alan adları",
|
||||
"malwarePhishingBlocked": "Engellenen zararlı içerikler",
|
||||
"blockedAdultWebsites": "Engellenen yetişkin içerikler",
|
||||
"generalSettings": "Genel ayarlar",
|
||||
"generalSettingsDescription": "Çeşitli farklı ayarları yönet",
|
||||
"hideZeroValues": "Sıfır değerlerini gizle",
|
||||
"hideZeroValuesDescription": "Ana ekranda, değeri sıfır olan blokları gizler.",
|
||||
"webAdminPanel": "Web yönetim paneli",
|
||||
"visitGooglePlay": "Google Play sayfasını ziyaret et",
|
||||
"gitHub": "Kaynak kodlarına GitHub'dan ulaşabilirsiniz",
|
||||
"blockClient": "İstemciyi engelle",
|
||||
"selectTags": "Etiketleri seçin",
|
||||
"noTagsSelected": "Seçili etiket yok",
|
||||
"tags": "Etiketler",
|
||||
"identifiers": "Tanımlayıcılar",
|
||||
"identifier": "Tanımlayıcı",
|
||||
"identifierHelper": "IP adresi, CIDR, MAC adresi veya ClientID",
|
||||
"noIdentifiers": "Tanımlayıcı eklenmedi",
|
||||
"useGlobalSettings": "Küresel ayarları kullan",
|
||||
"enableFiltering": "Filtrelemeyi etkinleştir",
|
||||
"enableSafeBrowsing": "Güvenli gezintiyi etkinleştir",
|
||||
"enableParentalControl": "Ebeveyn kontrolünü etkinleştir",
|
||||
"enableSafeSearch": "Güvenli aramayı aktif et",
|
||||
"blockedServices": "Engellenen hizmetler",
|
||||
"selectBlockedServices": "Engellenen hizmetleri seç",
|
||||
"noBlockedServicesSelected": "Engellenen hizmetler seçilmedi",
|
||||
"services": "Hizmetler",
|
||||
"servicesBlocked": "Hizmetler engellendi",
|
||||
"tagsSelected": "Seçilen etiketler",
|
||||
"upstreamServers": "Üst kaynak sunucuları",
|
||||
"serverAddress": "Sunucu adresi",
|
||||
"noUpstreamServers": "Üst kaynak sunucusu yok.",
|
||||
"willBeUsedGeneralServers": "Genel üst kaynak sunucuları kullanılacak.",
|
||||
"added": "Eklenenler",
|
||||
"clientUpdatedSuccessfully": "İstemci başarıyla güncellendi",
|
||||
"clientNotUpdated": "İstemci güncellenemedi",
|
||||
"clientDeletedSuccessfully": "İstemci başarıyla kaldırıldı",
|
||||
"clientNotDeleted": "İstemci silinemedi",
|
||||
"options": "Seçenekler",
|
||||
"loadingFilters": "Filtreler yükleniyor...",
|
||||
"filtersNotLoaded": "Filtreler yüklenemedi.",
|
||||
"whitelists": "Beyaz listeler",
|
||||
"blacklists": "Kara listeler",
|
||||
"rules": "Kurallar",
|
||||
"customRules": "Özel kurallar",
|
||||
"enabledRules": "Etkin kurallar",
|
||||
"enabled": "Etkin",
|
||||
"disabled": "Devre dışı",
|
||||
"rule": "Kural",
|
||||
"addCustomRule": "Özel kural ekle",
|
||||
"removeCustomRule": "Özel kuralı kaldır",
|
||||
"removeCustomRuleMessage": "Bu özel kuralı kaldırmak istediğinizden emin misiniz?",
|
||||
"updatingRules": "Özel kurallar güncelleniyor...",
|
||||
"ruleRemovedSuccessfully": "Kural başarıyla kaldırıldı",
|
||||
"ruleNotRemoved": "Kural kaldırılamadı",
|
||||
"ruleAddedSuccessfully": "Kural başarıyla eklendi",
|
||||
"ruleNotAdded": "Kural eklenemedi",
|
||||
"noCustomFilters": "Özel filtreler yok",
|
||||
"noBlockedClients": "Engellenmiş istemci yok",
|
||||
"noBlackLists": "Kara listeler yok",
|
||||
"noWhiteLists": "Beyaz listeler yok",
|
||||
"addWhitelist": "Beyaz liste ekle",
|
||||
"addBlacklist": "Kara liste ekle",
|
||||
"urlNotValid": "Bağlantı adresi geçerli değil",
|
||||
"urlAbsolutePath": "Bağlantı adresi veya kesin dosya yolu",
|
||||
"addingList": "Liste ekleniyor...",
|
||||
"listAdded": "Liste başarıyla eklendi. Eklenen öğeler:",
|
||||
"listAlreadyAdded": "Liste zaten eklenmiş",
|
||||
"listUrlInvalid": "Liste bağlantı adresi geçersiz",
|
||||
"listNotAdded": "Liste eklenemedi",
|
||||
"listDetails": "Liste detayları",
|
||||
"listType": "Liste türü",
|
||||
"whitelist": "Beyaz liste",
|
||||
"blacklist": "Kara liste",
|
||||
"latestUpdate": "Son güncelleme",
|
||||
"disable": "Devre dışı bırak",
|
||||
"enable": "Etkinleştir",
|
||||
"currentStatus": "Mevcut durum",
|
||||
"listDataUpdated": "Liste verileri başarıyla güncellendi",
|
||||
"listDataNotUpdated": "Liste verileri güncellenemedi",
|
||||
"updatingListData": "Liste verileri güncelleniyor...",
|
||||
"editWhitelist": "Beyaz listeyi düzenle",
|
||||
"editBlacklist": "Kara listeyi düzenle",
|
||||
"deletingList": "Liste siliniyor...",
|
||||
"listDeleted": "Liste başarıyla silindi",
|
||||
"listNotDeleted": "Liste silinemedi",
|
||||
"deleteList": "Listeyi sil",
|
||||
"deleteListMessage": "Bu listeyi silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
|
||||
"serverSettings": "Sunucu ayarları",
|
||||
"serverInformation": "Sunucu bilgisi",
|
||||
"serverInformationDescription": "Sunucu bilgisi ve durumunu öğren",
|
||||
"loadingServerInfo": "Sunucu bilgisi yükleniyor...",
|
||||
"serverInfoNotLoaded": "Sunucu bilgisi yüklenemedi.",
|
||||
"dnsAddresses": "DNS adresleri",
|
||||
"seeDnsAddresses": "DNS adreslerine göz at",
|
||||
"dnsPort": "DNS bağlantı noktası",
|
||||
"httpPort": "HTTP bağlantı noktası",
|
||||
"protectionEnabled": "Koruma etkin mi?",
|
||||
"dhcpAvailable": "DHCP mevcut mu?",
|
||||
"serverRunning": "Sunucu çalışıyor mu?",
|
||||
"serverVersion": "Sunucu sürümü",
|
||||
"serverLanguage": "Sunucu dili",
|
||||
"yes": "Evet",
|
||||
"no": "Hayır",
|
||||
"allowedClients": "İzin verilen istemciler",
|
||||
"disallowedClients": "İzin verilmeyen istemciler",
|
||||
"disallowedDomains": "İzin verilmeyen alan adları",
|
||||
"accessSettings": "Erişim ayarları",
|
||||
"accessSettingsDescription": "Sunucu için erişim kurallarını yapılandır",
|
||||
"loadingClients": "İstemciler yükleniyor...",
|
||||
"clientsNotLoaded": "İstemciler yüklenemedi.",
|
||||
"noAllowedClients": "İzin verilmiş istemci yok",
|
||||
"allowedClientsDescription": "Eğer bu liste girdiler içeriyorsa, AdGuard Home yalnızca bu istemcilerden gelen talepleri kabul edecektir.",
|
||||
"blockedClientsDescription": "Bu liste girdileri içeriyorsa, AdGuard Home bu istemcilerden gelen talepleri reddedecektir. Bu alan adı, İzin Verilen İstemciler'de girdi varsa görmezden gelinir.",
|
||||
"disallowedDomainsDescription": "AdGuard Home, bu alan adlarına uyan DNS sorgularını reddeder ve bu sorgular sorgu günlüğünde bile görünmez.",
|
||||
"addClientFieldDescription": "CIDR'ler, IP adresi veya ClientID",
|
||||
"clientIdentifier": "İstemci tanımlayıcısı",
|
||||
"allowClient": "İstemciye izin ver",
|
||||
"disallowClient": "İstemciye izin verme",
|
||||
"noDisallowedDomains": "İzin verilmeyen alan adı yok",
|
||||
"domainNotAdded": "Alan adı eklenemedi",
|
||||
"statusSelected": "Durum seçildi.",
|
||||
"updateLists": "Listeleri güncelle",
|
||||
"checkHostFiltered": "Filtrelemeyi kontrol et",
|
||||
"updatingLists": "Listeler güncelleniyor...",
|
||||
"listsUpdated": "Listeler güncellendi",
|
||||
"listsNotUpdated": "Listeler güncellenemedi",
|
||||
"listsNotLoaded": "Listeler yüklenemedi",
|
||||
"domainNotValid": "Alan adı geçersiz",
|
||||
"check": "Kontrol et",
|
||||
"checkingHost": "Kontrol ediliyor",
|
||||
"errorCheckingHost": "Kontrol etme başarısız",
|
||||
"block": "Engelle",
|
||||
"unblock": "Engeli kaldır",
|
||||
"custom": "Özel",
|
||||
"addImportant": "Ekle ($important)",
|
||||
"howCreateRules": "Özel kurallar nasıl oluşturulur?",
|
||||
"examples": "Örnekler",
|
||||
"example1": "example.org ve tüm alt alan adlarına erişimi engeller.",
|
||||
"example2": "example.org ve tüm alt alan adlarına erişimi engellemeyi kaldırır.",
|
||||
"example3": "Yorum ekler.",
|
||||
"example4": "Belirtilen düzenli ifadeye uyan alan adlarına erişimi engeller.",
|
||||
"moreInformation": "Daha fazla bilgi",
|
||||
"addingRule": "Kural ekleniyor...",
|
||||
"deletingRule": "Kural siliniyor...",
|
||||
"enablingList": "Liste etkinleştiriliyor...",
|
||||
"disablingList": "Liste devre dışı bırakılıyor...",
|
||||
"savingList": "Liste kaydediliyor...",
|
||||
"disableFiltering": "Filtrelemeyi devre dışı bırak",
|
||||
"enablingFiltering": "Filtreleme etkinleştiriliyor...",
|
||||
"disablingFiltering": "Filtreleme devre dışı bırakılıyor...",
|
||||
"filteringStatusUpdated": "Filtreleme durumu başarıyla güncellendi",
|
||||
"filteringStatusNotUpdated": "Filtreleme durumu güncellenemedi",
|
||||
"updateFrequency": "Güncelleme sıklığı",
|
||||
"never": "Asla",
|
||||
"hour1": "1 saat",
|
||||
"hours12": "12 saat",
|
||||
"hours24": "24 saat",
|
||||
"days3": "3 gün",
|
||||
"days7": "7 gün",
|
||||
"changingUpdateFrequency": "Değiştiriliyor...",
|
||||
"updateFrequencyChanged": "Güncelleme sıklığı başarıyla değiştirildi",
|
||||
"updateFrequencyNotChanged": "Güncelleme sıklığı değiştirilemedi",
|
||||
"updating": "Değerler güncelleniyor...",
|
||||
"blockedServicesUpdated": "Engellenen hizmetler başarıyla güncellendi",
|
||||
"blockedServicesNotUpdated": "Engellenen hizmetler güncellenemedi",
|
||||
"insertDomain": "Filtreleme durumunu kontrol etmek için bir alan adı ekleyin.",
|
||||
"dhcpSettings": "DHCP ayarları",
|
||||
"dhcpSettingsDescription": "DHCP sunucusunu yapılandır",
|
||||
"dhcpSettingsNotLoaded": "DHCP ayarları yüklenemedi",
|
||||
"loadingDhcp": "DHCP ayarları yükleniyor...",
|
||||
"enableDhcpServer": "DHCP sunucusunu etkinleştir",
|
||||
"selectInterface": "Arayüz seçin",
|
||||
"hardwareAddress": "Donanım adresi",
|
||||
"gatewayIp": "Ağ Geçidi IP'si",
|
||||
"ipv4addresses": "IPv4 adresleri",
|
||||
"ipv6addresses": "IPv6 adresleri",
|
||||
"neededSelectInterface": "DHCP sunucusunu yapılandırmak için bir arayüz seçmeniz gerekir.",
|
||||
"ipv4settings": "IPv4 ayarları",
|
||||
"startOfRange": "Menzilin başlangıcı",
|
||||
"endOfRange": "Menzilin sonu",
|
||||
"ipv6settings": "IPv6 ayarları",
|
||||
"subnetMask": "Alt ağ maskesi",
|
||||
"subnetMaskNotValid": "Alt ağ maskesi geçerli değil",
|
||||
"gateway": "Ağ Geçidi",
|
||||
"gatewayNotValid": "Ağ geçidi geçerli değil",
|
||||
"leaseTime": "Kira süresi",
|
||||
"seconds": "{time} saniye",
|
||||
"leaseTimeNotValid": "Kira süresi geçerli değil",
|
||||
"restoreConfiguration": "Yapılandırmayı sıfırla",
|
||||
"restoreConfigurationMessage": "Devam etmek istediğinizden emin misiniz? Bu, tüm yapılandırmayı sıfırlayacak. Bu işlem geri alınamaz.",
|
||||
"changeInterface": "Arayüzü değiştir",
|
||||
"savingSettings": "Ayarlar kaydediliyor...",
|
||||
"settingsSaved": "Ayarlar başarıyla kaydedildi",
|
||||
"settingsNotSaved": "Ayarlar kaydedilemedi",
|
||||
"restoringConfig": "Yapılandırma geri yükleniyor...",
|
||||
"configRestored": "Yapılandırma başarıyla sıfırlandı",
|
||||
"configNotRestored": "Yapılandırma sıfırlanamadı",
|
||||
"dhcpStatic": "DHCP statik kiralamaları",
|
||||
"noDhcpStaticLeases": "DHCP statik kiralamaları bulunamadı",
|
||||
"deleting": "Siliniyor...",
|
||||
"staticLeaseDeleted": "DHCP statik kiralama başarıyla silindi",
|
||||
"staticLeaseNotDeleted": "DHCP statik kiralaması silinemedi",
|
||||
"deleteStaticLease": "Statik kiralamayı sil",
|
||||
"deleteStaticLeaseDescription": "DHCP statik kirası silinecek. Bu işlem geri alınamaz.",
|
||||
"addStaticLease": "Statik kiralama ekleyin",
|
||||
"macAddress": "MAC adresi",
|
||||
"macAddressNotValid": "MAC adresi geçersiz",
|
||||
"hostName": "Ana bilgisayar adı",
|
||||
"hostNameError": "Ana bilgisayar adı boş olamaz",
|
||||
"creating": "Oluşturuluyor...",
|
||||
"staticLeaseCreated": "DHCP statik kiralaması başarıyla oluşturuldu",
|
||||
"staticLeaseNotCreated": "DHCP statik kiralaması oluşturulamadı",
|
||||
"staticLeaseExists": "DHCP statik kiralaması zaten mevcut",
|
||||
"serverNotConfigured": "Sunucu yapılandırılmamış",
|
||||
"restoreLeases": "Kiralamaları sıfırla",
|
||||
"restoreLeasesMessage": "Devam etmek istediğinizden emin misiniz? Bu, mevcut tüm kiralamaları sıfırlayacaktır. Bu işlem geri alınamaz.",
|
||||
"restoringLeases": "Kiralamalar sıfırlanıyor...",
|
||||
"leasesRestored": "Kiralamalar başarıyla sıfırlandı",
|
||||
"leasesNotRestored": "Kiralar sıfırlanamadı",
|
||||
"dhcpLeases": "DHCP kiralamaları",
|
||||
"noLeases": "Kullanılabilir DHCP kiralaması yok",
|
||||
"dnsRewrites": "DNS yeniden yazımları",
|
||||
"dnsRewritesDescription": "Özel DNS kurallarını yapılandır",
|
||||
"loadingRewriteRules": "Yeniden yazım kuralları yükleniyor...",
|
||||
"rewriteRulesNotLoaded": "DNS yeniden yazım kuralları yüklenemedi.",
|
||||
"noRewriteRules": "DNS yeniden yazım kuralları yok",
|
||||
"answer": "Yanıt",
|
||||
"deleteDnsRewrite": "DNS yeniden yazımı sil",
|
||||
"deleteDnsRewriteMessage": "Bu DNS yeniden yazımını silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
|
||||
"dnsRewriteRuleDeleted": "DNS yeniden yazım kuralı başarıyla silindi",
|
||||
"dnsRewriteRuleNotDeleted": "DNS yeniden yazım kuralı silinemedi",
|
||||
"addDnsRewrite": "DNS yeniden yazımı ekle",
|
||||
"addingRewrite": "Yeniden yazım ekleniyor...",
|
||||
"dnsRewriteRuleAdded": "DNS yeniden yazım kuralı başarıyla eklendi",
|
||||
"dnsRewriteRuleNotAdded": "DNS yeniden yazım kuralı eklenemedi",
|
||||
"logsSettings": "Günlük ayarları",
|
||||
"enableLog": "Günlüğü etkinleştir",
|
||||
"clearLogs": "Günlükleri temizle",
|
||||
"anonymizeClientIp": "İstemci IP'sini anonimleştir",
|
||||
"hours6": "6 saat",
|
||||
"days30": "30 gün",
|
||||
"days90": "90 gün",
|
||||
"retentionTime": "Saklama süresi",
|
||||
"selectOneItem": "Bir öğe seçin",
|
||||
"logSettingsNotLoaded": "Günlük ayarları yüklenemedi.",
|
||||
"updatingSettings": "Ayarlar güncelleniyor...",
|
||||
"logsConfigUpdated": "Günlük ayarları başarıyla güncellendi",
|
||||
"logsConfigNotUpdated": "Günlük ayarları başarıyla güncellendi",
|
||||
"deletingLogs": "Günlükler temizleniyor...",
|
||||
"logsCleared": "Günlükler başarıyla temizlendi",
|
||||
"logsNotCleared": "Günlükler temizlenemedi",
|
||||
"runningHomeAssistant": "Ev asistanı üzerinde çalıştır",
|
||||
"serverError": "Sunucu hatası",
|
||||
"noItems": "Burada gösterilecek öğe yok",
|
||||
"dnsSettings": "DNS ayarları",
|
||||
"dnsSettingsDescription": "DNS sunucuları ile bağlantıyı yapılandır",
|
||||
"upstreamDns": "Üst kaynak DNS sunucuları",
|
||||
"bootstrapDns": "Önyükleme DNS sunucuları",
|
||||
"noUpstreamDns": "Üst kaynak DNS sunucuları eklenmedi.",
|
||||
"dnsMode": "DNS modu",
|
||||
"noDnsMode": "DNS modu seçili değil",
|
||||
"loadBalancing": "Yük dengeleme",
|
||||
"parallelRequests": "Paralel istekler",
|
||||
"fastestIpAddress": "En hızlı IP adresi",
|
||||
"loadBalancingDescription": "Her seferinde bir üst kaynak sunucusuna sorgu yap. AdGuard Home, sunucuyu seçmek için ağırlıklı rastgele algoritmasını kullanır, böylece en hızlı sunucu daha sık kullanılır.",
|
||||
"parallelRequestsDescription": "Tüm üst kaynak sunucularını aynı anda sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanın.",
|
||||
"fastestIpAddressDescription": "Tüm DNS sunucularına sorgu yapın ve tüm yanıtlar arasında en hızlı IP adresini döndürün. Bu, AdGuard Home'un tüm DNS sunucularından yanıtları beklemesi gerektiği için DNS sorgularını yavaşlatır, ancak genel bağlantıyı iyileştirir.",
|
||||
"noBootstrapDns": "Önyükleme DNS sunucuları eklenmedi.",
|
||||
"bootstrapDnsServersInfo": "Önyükleme DNS sunucuları, üst kaynaklarda belirttiğiniz DoH/DoT çözümleyicilerinin IP adreslerini çözmek için kullanılır.",
|
||||
"privateReverseDnsServers": "Özel ters DNS sunucuları",
|
||||
"privateReverseDnsServersDescription": "AdGuard Home'un yerel PTR sorguları için kullandığı DNS sunucuları. Bu sunucular, özel IP aralıklarındaki adresler için ters DNS kullanarak PTR isteklerini çözmek için kullanılır, örneğin '192.168.12.34' olarak ayarlanmamışsa AdGuard Home, AdGuard Home'un kendi adresleri dışında, işletim sisteminizin varsayılan DNS çözümleyicilerinin adreslerini kullanır.",
|
||||
"reverseDnsDefault": "Varsayılan olarak, AdGuard Home aşağıdaki ters DNS çözümleyicilerini kullanır",
|
||||
"addItem": "Öğe ekle",
|
||||
"noServerAddressesAdded": "Sunucu adresleri eklenmedi.",
|
||||
"usePrivateReverseDnsResolvers": "Özel ters DNS çözümleyicilerini kullan",
|
||||
"usePrivateReverseDnsResolversDescription": "Bu üst kaynak sunucularını kullanarak yerel olarak sunulan adresler için ters DNS sorguları gerçekleştirin. Devre dışı bırakılırsa, AdGuard Home, DHCP, /etc/hosts vb. kaynaklardan bilinen istemciler dışında tüm PTR isteklerine NXDOMAIN yanıtı verir.",
|
||||
"enableReverseResolving": "İstemcilerin IP adreslerinin ters çözümlemesini etkinleştir",
|
||||
"enableReverseResolvingDescription": "İstemcilerin IP adreslerini karşılık gelen çözücülere PTR sorguları göndererek IP adreslerini tersine çözümleyerek (yerel istemciler için özel DNS sunucuları, genel IP adresine sahip istemciler için üst kaynak sunucuları) istemcilerin ana bilgisayar adlarını tersine çöz.",
|
||||
"dnsServerSettings": "AdGuard Home DNS sunucusu ayarları",
|
||||
"limitRequestsSecond": "Saniye başına sınırlama isteği",
|
||||
"valueNotNumber": "Değer bir sayı değil",
|
||||
"enableEdns": "EDNS istemci alt ağını etkinleştir",
|
||||
"enableEdnsDescription": "Kaynak yönü isteklerine EDNS İstemci Alt Ağı Seçeneği (ECS) ekleyin ve istemciler tarafından gönderilen değerleri sorgu günlüğüne kaydedin.",
|
||||
"enableDnssec": "DNSSEC'i etkinleştir",
|
||||
"enableDnssecDescription": "Giden DNS sorguları için DNSSEC özelliğini etkinleştir ve sonucu kontrol edin.(DNSSEC etkinleştirilmiş bir çözümleyici gerekli)",
|
||||
"disableResolvingIpv6": "IPv6 adreslerinin çözümlenmesini devre dışı bırak",
|
||||
"disableResolvingIpv6Description": "IPv6 adresleri için tüm DNS sorgularını bırakın (AAAA yazın) ve HTTPS yanıtlarından IPv6 ipuçlarını kaldırın.",
|
||||
"blockingMode": "Engelleme modu",
|
||||
"defaultMode": "Varsayılan",
|
||||
"defaultDescription": "Reklam engelleme tarzı bir kural tarafından engellendiğinde sıfır IP adresi ile yanıt verin. (A için 0.0.0.0; :: AAAA için) /etc/hosts tarzı bir kural tarafından engellendiğinde kuralda belirtilen IP adresi ile yanıt verin.",
|
||||
"refusedDescription": "REFUSED kodu ile yanıt verin.",
|
||||
"nxdomainDescription": "NXDOMAIN kodu ile yanıt verin.",
|
||||
"nullIp": "Boş IP",
|
||||
"nullIpDescription": "Sıfır IP adresi ile yanıt verin. (A için 0.0.0.0; :: AAAA için)",
|
||||
"customIp": "Özel IP",
|
||||
"customIpDescription": "Manuel olarak ayarlanmış bir IP adresi ile yanıt verin.",
|
||||
"dnsCacheConfig": "DNS önbellek yapılandırması",
|
||||
"cacheSize": "Önbellek boyutu",
|
||||
"inBytes": "Bayt olarak",
|
||||
"overrideMinimumTtl": "Minimum kullanım süresini geçersiz kıl",
|
||||
"overrideMinimumTtlDescription": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan minimum kullanım süresi değerini ayarlayın (saniye olarak)",
|
||||
"overrideMaximumTtl": "Maksimum kullanım süresini geçersiz kıl",
|
||||
"overrideMaximumTtlDescription": "DNS önbelleğindeki girişler için maksimum kullanım süresi değerini ayarlayın (saniye olarak)",
|
||||
"optimisticCaching": "İyimser önbelleğe alma",
|
||||
"optimisticCachingDescription": "Girişlerin süresi dolmuş olsa bile Adguard Home'un önbellekten yanıt vermesini sağlayın ve aynı zamanda bunları yenilemeye çalışın.",
|
||||
"loadingDnsConfig": "DNS yapılandırması yükleniyor...",
|
||||
"dnsConfigNotLoaded": "DNS yapılandırması yüklenemedi.",
|
||||
"blockingIpv4": "IPv4 engelleniyor",
|
||||
"blockingIpv4Description": "Engellenen bir A isteği için döndürülecek IP adresi",
|
||||
"blockingIpv6": "IPv6 engelleniyor",
|
||||
"blockingIpv6Description": "Engellenen bir AAAA isteği için döndürülecek IP adresi",
|
||||
"invalidIp": "Geçersiz IP adresi",
|
||||
"dnsConfigSaved": "DNS sunucusu yapılandırması başarıyla kaydedildi",
|
||||
"dnsConfigNotSaved": "DNS sunucusu yapılandırması kaydedilemedi",
|
||||
"savingConfig": "Yapılandırma kaydediliyor...",
|
||||
"someValueNotValid": "Bazı değerler geçerli değil",
|
||||
"upstreamDnsDescription": "Üst kaynak sunucularını ve DNS modunu yapılandır",
|
||||
"bootstrapDnsDescription": "Önyükleme DNS sunucularını yapılandır",
|
||||
"privateReverseDnsDescription": "Özel DNS çözümleyicileri yapılandır ve özel ters DNS çözümlemeyi etkinleştir",
|
||||
"dnsServerSettingsDescription": "Hız limiti, engelleme modu ve daha fazlasını yapılandır",
|
||||
"dnsCacheConfigDescription": "Sunucunun DNS önbelleğini nasıl yöneteceğini yapılandır",
|
||||
"comment": "Yorum",
|
||||
"address": "Adres",
|
||||
"commentsDescription": "Yorumlar her zaman # işareti ile başlar. Onu eklemenize gerek yok, otomatik olarak eklenir.",
|
||||
"encryptionSettings": "Şifreleme ayarları",
|
||||
"encryptionSettingsDescription": "Şifreleme (HTTPS/QUIC/TLS) desteği",
|
||||
"loadingEncryptionSettings": "Şifreleme ayarları yükleniyor...",
|
||||
"encryptionSettingsNotLoaded": "Şifreleme ayarları yüklenemedi.",
|
||||
"enableEncryption": "Şifrelemeyi etkinleştir",
|
||||
"enableEncryptionTypes": "HTTPS, DNS-over-HTTPS ve DNS-over-TLS",
|
||||
"enableEncryptionDescription": "Eğer şifreleme etkinleştirilmişse, AdGuard Home yönetici arayüzü HTTPS üzerinden çalışacaktır ve DNS sunucusu DNS üzerinden HTTPS ve TLS ile gelen isteklere yanıt verecektir.",
|
||||
"serverConfiguration": "Sunucu yapılandırması",
|
||||
"domainName": "Alan adı",
|
||||
"domainNameDescription": "Eğer ayarlanırsa, AdGuard Home istemci kimliklerini tespit eder, DDR sorgularına yanıt verir ve ek bağlantı doğrulamalarını gerçekleştirir. Ayarlanmazsa, bu özellikler devre dışı bırakılır. Sertifikadaki DNS adlarından biriyle eşleşmelidir.",
|
||||
"redirectHttps": "Otomatik olarak HTTPS'e yönlendir",
|
||||
"httpsPort": "HTTPS bağlantı noktası",
|
||||
"tlsPort": "DNS-over-TLS bağlantı noktası",
|
||||
"dnsOverQuicPort": "DNS-over-QUIC bağlantı noktası",
|
||||
"certificates": "Sertifikalar",
|
||||
"certificatesDescription": "Şifreleme kullanmak için, alan adınız için geçerli bir SSL sertifikası zinciri sağlamanız gereklidir. letsencrypt.org'dan ücretsiz bir sertifika alabilir veya güvenilir sertifika yetkililerinden satın alabilirsiniz.",
|
||||
"certificateFilePath": "Sertifika dosyası belirle",
|
||||
"pasteCertificateContent": "Sertifika içeriğini yapıştır",
|
||||
"certificatePath": "Sertifika dosya yolu",
|
||||
"certificateContent": "Sertifika içeriği",
|
||||
"privateKey": "Özel anahtarlar",
|
||||
"privateKeyFile": "Özel anahtar dosyası belirle",
|
||||
"pastePrivateKey": "Özel anahtar içeriğini yapıştır",
|
||||
"usePreviousKey": "Önceden kaydedilmiş anahtarı kullan",
|
||||
"privateKeyPath": "Özel anahtar dosya yolu",
|
||||
"invalidCertificate": "Geçersiz sertifika",
|
||||
"invalidPrivateKey": "Geçersiz özel anahtar",
|
||||
"validatingData": "Veri doğrulama",
|
||||
"dataValid": "Veri geçerli",
|
||||
"dataNotValid": "Veri geçersiz",
|
||||
"encryptionConfigSaved": "Şifreleme yapılandırması başarıyla kaydedildi",
|
||||
"encryptionConfigNotSaved": "Şifreleme yapılandırması kaydedilemedi",
|
||||
"configError": "Yapılandırma hatası",
|
||||
"enterOnlyCertificate": "Yalnızca sertifikayı girin. ---BEGIN--- ve ---END--- satırlarını girmeyin.",
|
||||
"enterOnlyPrivateKey": "Yalnızca anahtarı girin. ---BEGIN--- ve ---END--- satırlarını girmeyin.",
|
||||
"noItemsSearch": "Bu arama için hiçbir öğe yok.",
|
||||
"clearSearch": "Aramayı temizle",
|
||||
"exitSearch": "Aramadan çık",
|
||||
"searchClients": "İstemcileri ara",
|
||||
"noClientsSearch": "Bu arama ile ilgili hiçbir istemci bulunamadı.",
|
||||
"customization": "Özelleştirme",
|
||||
"customizationDescription": "Bu uygulamayı özelleştir",
|
||||
"color": "Renk",
|
||||
"useDynamicTheme": "Dinamik renk teması kullan",
|
||||
"red": "Kırmızı",
|
||||
"green": "Yeşil",
|
||||
"blue": "Mavi",
|
||||
"yellow": "Sarı",
|
||||
"orange": "Turuncu",
|
||||
"brown": "Kahverengi",
|
||||
"cyan": "Camgöbeği",
|
||||
"purple": "Mor",
|
||||
"pink": "Pembe",
|
||||
"deepOrange": "Koyu turuncu",
|
||||
"indigo": "Çivit mavisi",
|
||||
"useThemeColorStatus": "Durum için tema rengini kullan",
|
||||
"useThemeColorStatusDescription": "Yeşil ve kırmızı durum renklerini tema rengi ve gri ile değiştirir.",
|
||||
"invalidCertificateChain": "Geçersiz sertifika zinciri",
|
||||
"validCertificateChain": "Geçerli sertifika zinciri",
|
||||
"subject": "Konu",
|
||||
"issuer": "Veren",
|
||||
"expires": "Süresi dolacak",
|
||||
"validPrivateKey": "Geçerli özel anahtar",
|
||||
"expirationDate": "Son kullanma tarihi",
|
||||
"keysNotMatch": "Geçersiz bir sertifika veya anahtar: tls: özel anahtar genel anahtarla eşleşmiyor.",
|
||||
"timeLogs": "Günlüklerdeki işlem süresi",
|
||||
"timeLogsDescription": "Günlükler listesinde zaman yerine işlem süresini göster",
|
||||
"hostNames": "Ana bilgisayar adları",
|
||||
"keyType": "Anahtar türü",
|
||||
"updateAvailable": "Güncelleme mevcut",
|
||||
"installedVersion": "Yüklü sürüm",
|
||||
"newVersion": "Yeni sürüm",
|
||||
"source": "Kaynak",
|
||||
"downloadUpdate": "Güncellemeyi indir",
|
||||
"download": "İndir",
|
||||
"doNotRememberAgainUpdate": "Bu sürüm için tekrar hatırlama.",
|
||||
"downloadingUpdate": "İndiriliyor",
|
||||
"completed": "Tamamlandı",
|
||||
"permissionNotGranted": "İzin verilmedi",
|
||||
"inputSearchTerm": "Bir arama terimi girin.",
|
||||
"answers": "Yanıtlar",
|
||||
"copyClipboard": "Panoya kopyala",
|
||||
"domainCopiedClipboard": "Alan adı panoya kopyalandı",
|
||||
"clearDnsCache": "DNS önbelleğini temizle",
|
||||
"clearDnsCacheMessage": "DNS önbelleğini temizlemek istediğinizden emin misiniz?",
|
||||
"dnsCacheCleared": "DNS önbelleği başarıyla temizlendi",
|
||||
"clearingDnsCache": "Önbellek temizleniyor...",
|
||||
"dnsCacheNotCleared": "DNS önbelleği temizlenemedi",
|
||||
"clientsSelected": "Seçilmiş istemci",
|
||||
"invalidDomain": "Geçersiz alan adı",
|
||||
"loadingBlockedServicesList": "Engellenen hizmetler listesi yükleniyor...",
|
||||
"blockedServicesListNotLoaded": "Engellenen hizmetler listesi yüklenemedi",
|
||||
"error": "Hata",
|
||||
"updates": "Güncellemeler",
|
||||
"updatesDescription": "AdGuard Home sunucusunu güncelle",
|
||||
"updateNow": "Şimdi güncelle",
|
||||
"currentVersion": "Mevcut sürüm",
|
||||
"requestStartUpdateFailed": "Güncellemeyi başlatma isteği başarısız oldu",
|
||||
"requestStartUpdateSuccessful": "Güncellemeyi başlatma isteği başarılı",
|
||||
"serverUpdated": "Sunucu güncellendi",
|
||||
"unknownStatus": "Bilinmeyen durum",
|
||||
"checkingUpdates": "Güncellemeler kontrol ediliyor...",
|
||||
"checkUpdates": "Güncellemeleri kontrol et",
|
||||
"requestingUpdate": "Güncelleme talep ediliyor...",
|
||||
"autoupdateUnavailable": "Otomatik güncelleme kullanılamıyor",
|
||||
"autoupdateUnavailableDescription": "Otomatik güncelleme servisi bu sunucu için kullanılamıyor. Bunun nedeni sunucunun bir Docker konteynerinde çalışıyor olması olabilir. Sunucunuzu manuel olarak güncellemeniz gerekecektir.",
|
||||
"minute": "{time} dakika",
|
||||
"minutes": "{time} dakika",
|
||||
"hour": "{time} saat",
|
||||
"hours": "{time} saat",
|
||||
"remainingTime": "Kalan süre",
|
||||
"safeSearchSettings": "Güvenli arama ayarları",
|
||||
"loadingSafeSearchSettings": "Güvenli arama ayarları yükleniyor...",
|
||||
"safeSearchSettingsNotLoaded": "Güvenli arama ayarları yüklenirken hata oluştu.",
|
||||
"loadingLogsSettings": "Günlük ayarları yükleniyor...",
|
||||
"selectOptionLeftColumn": "Sol sütundan bir seçenek seçin",
|
||||
"selectClientLeftColumn": "Sol sütundan bir istemci seçin",
|
||||
"disableList": "Listeyi devre dışı bırak",
|
||||
"enableList": "Listeyi etkinleştir",
|
||||
"screens": "Ekranlar",
|
||||
"copiedClipboard": "Panoya kopyalandı",
|
||||
"seeDetails": "Detayları gör",
|
||||
"listNotAvailable": "Liste mevcut değil",
|
||||
"copyListUrl": "Liste bağlantısını kopyala",
|
||||
"listUrlCopied": "Panoya kopyalanan bağlantı adresini listeleyin",
|
||||
"unsupportedVersion": "Desteklenmeyen sürüm",
|
||||
"unsupprtedVersionMessage": "Sunucu sürümünüz {version} için destek garantisi verilmiyor. Bu uygulamanın bu sunucu sürümüyle çalışmasında bazı sorunlar olabilir. AdGuard Home Yöneticisi, AdGuard Home sunucunun kararlı sürümleriyle çalışacak şekilde tasarlanmıştır. Alfa ve beta sürümleriyle çalışabilir, ancak uyumluluk garanti edilmez ve uygulama bu sürümlerle çalışırken bazı sorunlar yaşayabilir.",
|
||||
"iUnderstand": "Anladım",
|
||||
"appUpdates": "Uygulama güncellemeleri",
|
||||
"usingLatestVersion": "En son sürümü kullanıyorsunuz :)",
|
||||
"ipLogs": "Günlüklerdeki IP",
|
||||
"ipLogsDescription": "Günlükler listesinde istemci adı yerine IP adresini göster",
|
||||
"application": "Uygulama",
|
||||
"combinedChart": "Birleştirilmiş grafik",
|
||||
"combinedChartDescription": "Tüm grafikleri bir araya getirir.",
|
||||
"statistics": "İstatistikler",
|
||||
"errorLoadFilters": "Filtreler yüklenirken hata oluştu.",
|
||||
"clientRemovedSuccessfully": "İstemci başarıyla kaldırıldı.",
|
||||
"editRewriteRule": "Yeniden yazım kuralını düzenle",
|
||||
"dnsRewriteRuleUpdated": "DNS yeniden yazım kuralı başarıyla güncellendi",
|
||||
"dnsRewriteRuleNotUpdated": "DNS yeniden yazım kuralı güncellenemedi",
|
||||
"updatingRule": "Kural güncelleniyor...",
|
||||
"serverUpdateNeeded": "Sunucu güncellemesi gerekli",
|
||||
"updateYourServer": "Bu özelliği kullanmak için AdGuard Home sunucunuzu {version} veya üzeri bir sürüme güncelleyin.",
|
||||
"january": "Ocak",
|
||||
"february": "Şubat",
|
||||
"march": "Mart",
|
||||
"april": "Nisan",
|
||||
"may": "Mayıs",
|
||||
"june": "Haziran",
|
||||
"july": "Temmuz",
|
||||
"august": "Ağustos",
|
||||
"september": "Eylül",
|
||||
"october": "Ekim",
|
||||
"november": "Kasım",
|
||||
"december": "Aralık",
|
||||
"malwarePhishing": "Zararlı yazılım/oltalama",
|
||||
"queries": "Sorgular",
|
||||
"adultSites": "Yetişkin içerikler",
|
||||
"quickFilters": "Hızlı filtreler",
|
||||
"searchDomainInternet": "İnternette alan adı ara",
|
||||
"hideServerAddress": "Sunucu adresini gizle",
|
||||
"hideServerAddressDescription": "Ana ekranda sunucu adresini gizler.",
|
||||
"topItemsOrder": "Öne çıkan öğeler sıralaması",
|
||||
"topItemsOrderDescription": "Ana ekrandaki öne çıkan öğe listelerini sırala",
|
||||
"topItemsReorderInfo": "Yeniden sıralamak için bir öğeyi basılı tutun ve kaydırın.",
|
||||
"discardChanges": "Değişiklikleri iptal et",
|
||||
"discardChangesDescription": "Değişiklikleri iptal etmek istediğinizden emin misiniz?",
|
||||
"others": "Diğerleri",
|
||||
"showChart": "Göster",
|
||||
"hideChart": "Gizle",
|
||||
"showTopItemsChart": "Öne çıkan öğeler grafiği",
|
||||
"showTopItemsChartDescription": "Varsayılan olarak öne çıkan öğeler bölümünde halka grafiğini gösterir. Sadece mobil görünümü etkiler.",
|
||||
"openMenu": "Menüyü genişlet",
|
||||
"closeMenu": "Menüyü daralt",
|
||||
"openListUrl": "Liste bağlantısını aç",
|
||||
"selectionMode": "Seçim modu",
|
||||
"enableDisableSelected": "Seçili öğeleri etkinleştir veya devre dışı bırak",
|
||||
"deleteSelected": "Seçili öğeleri sil",
|
||||
"deleteSelectedLists": "Seçili listeleri sil",
|
||||
"allSelectedListsDeletedSuccessfully": "Seçilen tüm listeler başarıyla silindi.",
|
||||
"deletionResult": "Silinme sonucu",
|
||||
"deletingLists": "Listeler siliniyor...",
|
||||
"failedElements": "Başarısız öğeler",
|
||||
"processingLists": "Listeler işleniyor...",
|
||||
"enableDisableResult": "Sonucu etkinleştir veya devre dışı bırak",
|
||||
"selectedListsEnabledDisabledSuccessfully": "Seçilen tüm listeler başarıyla etkinleştirildi veya devre dışı bırakıldı",
|
||||
"sslWarning": "Kendinden imzalı bir sertifika ile HTTPS bağlantısı kullanıyorsanız, Ayarlar > Gelişmiş ayarlar bölümünde \"SSL sertifikasını asla kontrol etme\" seçeneğini etkinleştirdiğinizden emin olun.",
|
||||
"unsupportedServerVersion": "Desteklenmeyen sunucu sürümü",
|
||||
"unsupportedServerVersionMessage": "AdGuard Home sunucu sürümünüz çok eski ve AdGuard Home Manager tarafından desteklenmiyor. Bu uygulamayı kullanmak için AdGuard Home sunucunuzu daha yeni bir sürüme yükseltmeniz gerekecektir.",
|
||||
"yourVersion": "Yüklü sürüm: {version}",
|
||||
"minimumRequiredVersion": "Gerekli minimum sürüm: {version}",
|
||||
"topUpstreams": "Öne çıkan üst kaynaklar",
|
||||
"averageUpstreamResponseTime": "Üst kaynak ortalama yanıt süresi"
|
||||
}
|
|
@ -170,7 +170,7 @@
|
|||
"dnsQueries": "DNS 查询",
|
||||
"average": "平均值",
|
||||
"blockedFilters": "被过滤器拦截",
|
||||
"malwarePhisingBlocked": "被拦截的恶意/钓鱼网站",
|
||||
"malwarePhishingBlocked": "被拦截的恶意/钓鱼网站",
|
||||
"blockedAdultWebsites": "被拦截的成人网站",
|
||||
"generalSettings": "常规设置",
|
||||
"generalSettingsDescription": "各种不同的设置",
|
||||
|
@ -649,9 +649,16 @@
|
|||
"october": "10月",
|
||||
"november": "11月",
|
||||
"december": "12月",
|
||||
"malwarePhising": "恶意/钓鱼网站",
|
||||
"malwarePhishing": "恶意/钓鱼网站",
|
||||
"queries": "查询",
|
||||
"adultSites": "成人网站",
|
||||
"quickFilters": "状态过滤器",
|
||||
"searchDomainInternet": "在互联网上搜索该域名"
|
||||
"searchDomainInternet": "在互联网上搜索该域名",
|
||||
"hideServerAddress": "隐藏服务器地址",
|
||||
"hideServerAddressDescription": "在主页上隐藏服务器地址",
|
||||
"topItemsOrder": "顶部项目顺序",
|
||||
"topItemsOrderDescription": "排列主页顶部项目列表",
|
||||
"topItemsReorderInfo": "按住并滑动一个项目以重新排序。",
|
||||
"discardChanges": "放弃更改",
|
||||
"discardChangesDescription": "您确定要放弃更改吗?"
|
||||
}
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
"dnsQueries": "DNS 查询",
|
||||
"average": "平均值",
|
||||
"blockedFilters": "被过滤器拦截",
|
||||
"malwarePhisingBlocked": "被拦截的恶意/钓鱼网站",
|
||||
"malwarePhishingBlocked": "被拦截的恶意/钓鱼网站",
|
||||
"blockedAdultWebsites": "被拦截的成人网站",
|
||||
"generalSettings": "常规设置",
|
||||
"generalSettingsDescription": "各种不同的设置",
|
||||
|
@ -649,9 +649,16 @@
|
|||
"october": "10月",
|
||||
"november": "11月",
|
||||
"december": "12月",
|
||||
"malwarePhising": "恶意/钓鱼网站",
|
||||
"malwarePhishing": "恶意/钓鱼网站",
|
||||
"queries": "查询",
|
||||
"adultSites": "成人网站",
|
||||
"quickFilters": "状态过滤器",
|
||||
"searchDomainInternet": "在互联网上搜索该域名"
|
||||
"searchDomainInternet": "在互联网上搜索该域名",
|
||||
"hideServerAddress": "隐藏服务器地址",
|
||||
"hideServerAddressDescription": "在主页上隐藏服务器地址",
|
||||
"topItemsOrder": "顶部项目顺序",
|
||||
"topItemsOrderDescription": "排列主页顶部项目列表",
|
||||
"topItemsReorderInfo": "按住并滑动一个项目以重新排序。",
|
||||
"discardChanges": "放弃更改",
|
||||
"discardChangesDescription": "您确定要放弃更改吗?"
|
||||
}
|
||||
|
|
|
@ -2,9 +2,10 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
@ -15,7 +16,8 @@ import 'package:window_size/window_size.dart';
|
|||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/base.dart';
|
||||
import 'package:adguard_home_manager/widgets/layout.dart';
|
||||
import 'package:adguard_home_manager/widgets/menu_bar.dart';
|
||||
|
||||
import 'package:adguard_home_manager/providers/logs_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
|
@ -34,6 +36,7 @@ import 'package:adguard_home_manager/services/db/database.dart';
|
|||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
|
||||
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
||||
setWindowMinSize(const Size(500, 500));
|
||||
|
@ -196,6 +199,7 @@ class _MainState extends State<Main> {
|
|||
@override
|
||||
void initState() {
|
||||
displayMode();
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -230,22 +234,16 @@ class _MainState extends State<Main> {
|
|||
Locale('zh', ''),
|
||||
Locale('zh', 'CN'),
|
||||
Locale('pl', ''),
|
||||
Locale('tr', ''),
|
||||
Locale('ru', '')
|
||||
],
|
||||
scaffoldMessengerKey: scaffoldMessengerKey,
|
||||
builder: (context, child) {
|
||||
return MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaleFactor: !(Platform.isAndroid || Platform.isIOS)
|
||||
? 0.9
|
||||
: 1.0
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
home: const Base(),
|
||||
navigatorKey: globalNavigatorKey,
|
||||
builder: (context, child) => CustomMenuBar(
|
||||
child: child!,
|
||||
),
|
||||
home: const Layout(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -4,14 +4,14 @@ class AppScreen {
|
|||
final String name;
|
||||
final IconData icon;
|
||||
final PreferredSizeWidget? appBar;
|
||||
final Widget body;
|
||||
final Widget? fab;
|
||||
final Widget child;
|
||||
|
||||
const AppScreen({
|
||||
required this.name,
|
||||
required this.icon,
|
||||
this.appBar,
|
||||
required this.body,
|
||||
this.fab
|
||||
this.fab,
|
||||
required this.child,
|
||||
});
|
||||
}
|
|
@ -84,10 +84,13 @@ class Client {
|
|||
final bool filteringEnabled;
|
||||
final bool parentalEnabled;
|
||||
final bool safebrowsingEnabled;
|
||||
final bool? safesearchEnabled;
|
||||
final bool useGlobalBlockedServices;
|
||||
final bool useGlobalSettings;
|
||||
final SafeSearch? safeSearch;
|
||||
final bool? ignoreQuerylog;
|
||||
final bool? ignoreStatistics;
|
||||
final bool? upstreamsCacheEnabled;
|
||||
final int? upstreamsCacheSize;
|
||||
|
||||
Client({
|
||||
required this.name,
|
||||
|
@ -98,10 +101,13 @@ class Client {
|
|||
required this.filteringEnabled,
|
||||
required this.parentalEnabled,
|
||||
required this.safebrowsingEnabled,
|
||||
required this.safesearchEnabled,
|
||||
required this.useGlobalBlockedServices,
|
||||
required this.useGlobalSettings,
|
||||
required this.safeSearch,
|
||||
required this.ignoreQuerylog,
|
||||
required this.ignoreStatistics,
|
||||
required this.upstreamsCacheEnabled,
|
||||
required this.upstreamsCacheSize,
|
||||
});
|
||||
|
||||
factory Client.fromJson(Map<String, dynamic> json) => Client(
|
||||
|
@ -113,12 +119,15 @@ class Client {
|
|||
filteringEnabled: json["filtering_enabled"],
|
||||
parentalEnabled: json["parental_enabled"],
|
||||
safebrowsingEnabled: json["safebrowsing_enabled"],
|
||||
safesearchEnabled: json["safesearch_enabled"],
|
||||
useGlobalBlockedServices: json["use_global_blocked_services"],
|
||||
useGlobalSettings: json["use_global_settings"],
|
||||
safeSearch: json["safe_search"] != null
|
||||
? SafeSearch.fromJson(json["safe_search"])
|
||||
: null
|
||||
: null,
|
||||
ignoreQuerylog: json["ignore_querylog"],
|
||||
ignoreStatistics: json["ignore_statistics"],
|
||||
upstreamsCacheEnabled: json["upstreams_cache_enabled"],
|
||||
upstreamsCacheSize: json["upstreams_cache_size"]
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
|
@ -130,9 +139,12 @@ class Client {
|
|||
"filtering_enabled": filteringEnabled,
|
||||
"parental_enabled": parentalEnabled,
|
||||
"safebrowsing_enabled": safebrowsingEnabled,
|
||||
"safesearch_enabled": safesearchEnabled,
|
||||
"safe_search": safeSearch,
|
||||
"use_global_blocked_services": useGlobalBlockedServices,
|
||||
"use_global_settings": useGlobalSettings,
|
||||
"ignore_querylog": ignoreQuerylog,
|
||||
"ignore_statistics": ignoreStatistics,
|
||||
"upstreams_cache_enabled": upstreamsCacheEnabled,
|
||||
"upstreams_cache_size": upstreamsCacheSize
|
||||
};
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
import 'dart:convert';
|
||||
class DhcpModel {
|
||||
bool dhcpAvailable;
|
||||
List<NetworkInterface> networkInterfaces;
|
||||
DhcpStatus dhcpStatus;
|
||||
DhcpStatus? dhcpStatus;
|
||||
|
||||
DhcpModel({
|
||||
required this.dhcpAvailable,
|
||||
required this.networkInterfaces,
|
||||
required this.dhcpStatus,
|
||||
});
|
||||
|
@ -72,11 +74,11 @@ class DhcpStatus {
|
|||
|
||||
factory DhcpStatus.fromJson(Map<String, dynamic> json) => DhcpStatus(
|
||||
interfaceName: json["interface_name"],
|
||||
v4: IpVersion.fromJson(json["v4"]),
|
||||
v6: IpVersion.fromJson(json["v6"]),
|
||||
v4: json["v4"] != null ? IpVersion.fromJson(json["v4"]) : null,
|
||||
v6: json["v6"] != null ? IpVersion.fromJson(json["v6"]) : null,
|
||||
leases: List<Lease>.from(json["leases"].map((x) => Lease.fromJson(x))),
|
||||
staticLeases: List<Lease>.from(json["static_leases"].map((x) => Lease.fromJson(x))),
|
||||
enabled: json["enabled"],
|
||||
enabled: json["enabled"] ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
|
|
|
@ -2,32 +2,39 @@ class DnsInfo {
|
|||
List<String> upstreamDns;
|
||||
String? upstreamDnsFile;
|
||||
List<String> bootstrapDns;
|
||||
List<String>? fallbackDns;
|
||||
bool protectionEnabled;
|
||||
int ratelimit;
|
||||
String blockingMode;
|
||||
bool ednsCsEnabled;
|
||||
bool? ednsCsUseCustom;
|
||||
String? ednsCsCustomIp;
|
||||
bool dnssecEnabled;
|
||||
bool disableIpv6;
|
||||
String? upstreamMode;
|
||||
int? cacheSize;
|
||||
int? cacheTtlMin;
|
||||
int? cacheTtlMax;
|
||||
bool cacheOptimistic;
|
||||
bool resolveClients;
|
||||
bool usePrivatePtrResolvers;
|
||||
bool? cacheOptimistic;
|
||||
bool? resolveClients;
|
||||
bool? usePrivatePtrResolvers;
|
||||
List<String> localPtrUpstreams;
|
||||
String blockingIpv4;
|
||||
String blockingIpv6;
|
||||
List<String> defaultLocalPtrUpstreams;
|
||||
int? blockedResponseTtl;
|
||||
|
||||
DnsInfo({
|
||||
required this.upstreamDns,
|
||||
required this.upstreamDnsFile,
|
||||
required this.bootstrapDns,
|
||||
required this.fallbackDns,
|
||||
required this.protectionEnabled,
|
||||
required this.ratelimit,
|
||||
required this.blockingMode,
|
||||
required this.ednsCsEnabled,
|
||||
required this.ednsCsUseCustom,
|
||||
required this.ednsCsCustomIp,
|
||||
required this.dnssecEnabled,
|
||||
required this.disableIpv6,
|
||||
required this.upstreamMode,
|
||||
|
@ -41,16 +48,20 @@ class DnsInfo {
|
|||
required this.blockingIpv4,
|
||||
required this.blockingIpv6,
|
||||
required this.defaultLocalPtrUpstreams,
|
||||
required this.blockedResponseTtl,
|
||||
});
|
||||
|
||||
factory DnsInfo.fromJson(Map<String, dynamic> json) => DnsInfo(
|
||||
upstreamDns: json["upstream_dns"] != null ? List<String>.from(json["upstream_dns"].map((x) => x)) : [],
|
||||
upstreamDnsFile: json["upstream_dns_file"],
|
||||
bootstrapDns: json["bootstrap_dns"] != null ? List<String>.from(json["bootstrap_dns"].map((x) => x)) : [],
|
||||
fallbackDns: json["fallback_dns"] != null ? List<String>.from(json["fallback_dns"].map((x) => x)) : [],
|
||||
protectionEnabled: json["protection_enabled"],
|
||||
ratelimit: json["ratelimit"],
|
||||
blockingMode: json["blocking_mode"],
|
||||
ednsCsEnabled: json["edns_cs_enabled"],
|
||||
ednsCsUseCustom: json["edns_cs_use_custom"],
|
||||
ednsCsCustomIp: json["edns_cs_custom_ip"],
|
||||
dnssecEnabled: json["dnssec_enabled"],
|
||||
disableIpv6: json["disable_ipv6"],
|
||||
upstreamMode: json["upstream_mode"],
|
||||
|
@ -64,16 +75,20 @@ class DnsInfo {
|
|||
blockingIpv4: json["blocking_ipv4"],
|
||||
blockingIpv6: json["blocking_ipv6"],
|
||||
defaultLocalPtrUpstreams: json["default_local_ptr_upstreams"] != null ? List<String>.from(json["default_local_ptr_upstreams"].map((x) => x)) : [],
|
||||
blockedResponseTtl: json["blocked_response_ttl"]
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"upstream_dns": List<dynamic>.from(upstreamDns.map((x) => x)),
|
||||
"upstream_dns_file": upstreamDnsFile,
|
||||
"bootstrap_dns": List<dynamic>.from(bootstrapDns.map((x) => x)),
|
||||
"fallback_dns": List<dynamic>.from(bootstrapDns.map((x) => x)),
|
||||
"protection_enabled": protectionEnabled,
|
||||
"ratelimit": ratelimit,
|
||||
"blocking_mode": blockingMode,
|
||||
"edns_cs_enabled": ednsCsEnabled,
|
||||
"edns_cs_use_custom": ednsCsUseCustom,
|
||||
"edns_cs_custom_ip": ednsCsCustomIp,
|
||||
"dnssec_enabled": dnssecEnabled,
|
||||
"disable_ipv6": disableIpv6,
|
||||
"upstream_mode": upstreamMode,
|
||||
|
@ -87,5 +102,6 @@ class DnsInfo {
|
|||
"blocking_ipv4": blockingIpv4,
|
||||
"blocking_ipv6": blockingIpv6,
|
||||
"default_local_ptr_upstreams": List<dynamic>.from(defaultLocalPtrUpstreams.map((x) => x)),
|
||||
"blocked_response_ttl": blockedResponseTtl
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ class DnsStatistics {
|
|||
final List<Map<String, int>> topQueriedDomains;
|
||||
final List<Map<String, int>> topClients;
|
||||
final List<Map<String, int>> topBlockedDomains;
|
||||
final List<Map<String, int>>? topUpstreamResponses;
|
||||
final List<Map<String, double>>? topUpstreamsAvgTime;
|
||||
final List<int> dnsQueries;
|
||||
final List<int> blockedFiltering;
|
||||
final List<int> replacedSafebrowsing;
|
||||
|
@ -25,6 +27,8 @@ class DnsStatistics {
|
|||
required this.topQueriedDomains,
|
||||
required this.topClients,
|
||||
required this.topBlockedDomains,
|
||||
required this.topUpstreamResponses,
|
||||
required this.topUpstreamsAvgTime,
|
||||
required this.dnsQueries,
|
||||
required this.blockedFiltering,
|
||||
required this.replacedSafebrowsing,
|
||||
|
@ -42,6 +46,8 @@ class DnsStatistics {
|
|||
topQueriedDomains: List<Map<String, int>>.from(json["top_queried_domains"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))),
|
||||
topClients: List<Map<String, int>>.from(json["top_clients"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))),
|
||||
topBlockedDomains: List<Map<String, int>>.from(json["top_blocked_domains"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))),
|
||||
topUpstreamResponses: json["top_upstreams_responses"] != null ? List<Map<String, int>>.from(json["top_upstreams_responses"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))) : null,
|
||||
topUpstreamsAvgTime: json["top_upstreams_avg_time"] != null ? List<Map<String, double>>.from(json["top_upstreams_avg_time"].map((x) => Map.from(x).map((k, v) => MapEntry<String, double>(k, v)))) : null,
|
||||
dnsQueries: List<int>.from(json["dns_queries"].map((x) => x)),
|
||||
blockedFiltering: List<int>.from(json["blocked_filtering"].map((x) => x)),
|
||||
replacedSafebrowsing: List<int>.from(json["replaced_safebrowsing"].map((x) => x)),
|
||||
|
@ -59,6 +65,8 @@ class DnsStatistics {
|
|||
"top_queried_domains": List<dynamic>.from(topQueriedDomains.map((x) => Map.from(x).map((k, v) => MapEntry<String, dynamic>(k, v)))),
|
||||
"top_clients": List<dynamic>.from(topClients.map((x) => Map.from(x).map((k, v) => MapEntry<String, dynamic>(k, v)))),
|
||||
"top_blocked_domains": List<dynamic>.from(topBlockedDomains.map((x) => Map.from(x).map((k, v) => MapEntry<String, dynamic>(k, v)))),
|
||||
"top_upstreams_responses": topUpstreamResponses != null ? List<dynamic>.from(topUpstreamResponses!.map((x) => Map.from(x).map((k, v) => MapEntry<String, dynamic>(k, v)))) : null,
|
||||
"top_upstreams_avg_time": topUpstreamsAvgTime != null ? List<dynamic>.from(topUpstreamsAvgTime!.map((x) => Map.from(x).map((k, v) => MapEntry<String, dynamic>(k, v)))) : null,
|
||||
"dns_queries": List<dynamic>.from(dnsQueries.map((x) => x)),
|
||||
"blocked_filtering": List<dynamic>.from(blockedFiltering.map((x) => x)),
|
||||
"replaced_safebrowsing": List<dynamic>.from(replacedSafebrowsing.map((x) => x)),
|
||||
|
|
|
@ -122,3 +122,128 @@ class EncryptionData {
|
|||
"private_key_saved": privateKeySaved,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
class EncryptionValidationResult {
|
||||
final bool isObject;
|
||||
final EncryptionValidation? encryptionValidation;
|
||||
final String? message;
|
||||
|
||||
const EncryptionValidationResult({
|
||||
required this.isObject,
|
||||
this.encryptionValidation,
|
||||
this.message
|
||||
});
|
||||
}
|
||||
|
||||
class EncryptionValidation {
|
||||
final String? subject;
|
||||
final String? issuer;
|
||||
final String? keyType;
|
||||
final DateTime? notBefore;
|
||||
final DateTime? notAfter;
|
||||
final String? warningValidation;
|
||||
final List<String>? dnsNames;
|
||||
final bool? validCert;
|
||||
final bool? validChain;
|
||||
final bool? validKey;
|
||||
final bool? validPair;
|
||||
final bool? enabled;
|
||||
final String? serverName;
|
||||
final bool? forceHttps;
|
||||
final int? portHttps;
|
||||
final int? portDnsOverTls;
|
||||
final int? portDnsOverQuic;
|
||||
final int? portDnscrypt;
|
||||
final String? dnscryptConfigFile;
|
||||
final bool? allowUnencryptedDoh;
|
||||
final String? certificateChain;
|
||||
final String? privateKey;
|
||||
final String? certificatePath;
|
||||
final String? privateKeyPath;
|
||||
final bool? privateKeySaved;
|
||||
|
||||
EncryptionValidation({
|
||||
this.subject,
|
||||
this.issuer,
|
||||
this.keyType,
|
||||
this.notBefore,
|
||||
this.notAfter,
|
||||
this.warningValidation,
|
||||
this.dnsNames,
|
||||
this.validCert,
|
||||
this.validChain,
|
||||
this.validKey,
|
||||
this.validPair,
|
||||
this.enabled,
|
||||
this.serverName,
|
||||
this.forceHttps,
|
||||
this.portHttps,
|
||||
this.portDnsOverTls,
|
||||
this.portDnsOverQuic,
|
||||
this.portDnscrypt,
|
||||
this.dnscryptConfigFile,
|
||||
this.allowUnencryptedDoh,
|
||||
this.certificateChain,
|
||||
this.privateKey,
|
||||
this.certificatePath,
|
||||
this.privateKeyPath,
|
||||
this.privateKeySaved,
|
||||
});
|
||||
|
||||
factory EncryptionValidation.fromJson(Map<String, dynamic> json) => EncryptionValidation(
|
||||
subject: json["subject"],
|
||||
issuer: json["issuer"],
|
||||
keyType: json["key_type"],
|
||||
notBefore: json["not_before"] == null ? null : DateTime.parse(json["not_before"]),
|
||||
notAfter: json["not_after"] == null ? null : DateTime.parse(json["not_after"]),
|
||||
warningValidation: json["warning_validation"],
|
||||
dnsNames: json["dns_names"] == null ? [] : List<String>.from(json["dns_names"]!.map((x) => x)),
|
||||
validCert: json["valid_cert"],
|
||||
validChain: json["valid_chain"],
|
||||
validKey: json["valid_key"],
|
||||
validPair: json["valid_pair"],
|
||||
enabled: json["enabled"],
|
||||
serverName: json["server_name"],
|
||||
forceHttps: json["force_https"],
|
||||
portHttps: json["port_https"],
|
||||
portDnsOverTls: json["port_dns_over_tls"],
|
||||
portDnsOverQuic: json["port_dns_over_quic"],
|
||||
portDnscrypt: json["port_dnscrypt"],
|
||||
dnscryptConfigFile: json["dnscrypt_config_file"],
|
||||
allowUnencryptedDoh: json["allow_unencrypted_doh"],
|
||||
certificateChain: json["certificate_chain"],
|
||||
privateKey: json["private_key"],
|
||||
certificatePath: json["certificate_path"],
|
||||
privateKeyPath: json["private_key_path"],
|
||||
privateKeySaved: json["private_key_saved"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"subject": subject,
|
||||
"issuer": issuer,
|
||||
"key_type": keyType,
|
||||
"not_before": notBefore?.toIso8601String(),
|
||||
"not_after": notAfter?.toIso8601String(),
|
||||
"warning_validation": warningValidation,
|
||||
"dns_names": dnsNames == null ? [] : List<dynamic>.from(dnsNames!.map((x) => x)),
|
||||
"valid_cert": validCert,
|
||||
"valid_chain": validChain,
|
||||
"valid_key": validKey,
|
||||
"valid_pair": validPair,
|
||||
"enabled": enabled,
|
||||
"server_name": serverName,
|
||||
"force_https": forceHttps,
|
||||
"port_https": portHttps,
|
||||
"port_dns_over_tls": portDnsOverTls,
|
||||
"port_dns_over_quic": portDnsOverQuic,
|
||||
"port_dnscrypt": portDnscrypt,
|
||||
"dnscrypt_config_file": dnscryptConfigFile,
|
||||
"allow_unencrypted_doh": allowUnencryptedDoh,
|
||||
"certificate_chain": certificateChain,
|
||||
"private_key": privateKey,
|
||||
"certificate_path": certificatePath,
|
||||
"private_key_path": privateKeyPath,
|
||||
"private_key_saved": privateKeySaved,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class Filtering {
|
||||
final List<Filter> filters;
|
||||
final List<Filter> whitelistFilters;
|
||||
List<Filter> filters;
|
||||
List<Filter> whitelistFilters;
|
||||
List<String> userRules;
|
||||
List<String> blockedServices;
|
||||
int interval;
|
||||
|
@ -40,7 +40,7 @@ class Filter {
|
|||
final DateTime? lastUpdated;
|
||||
final int id;
|
||||
final int rulesCount;
|
||||
final bool enabled;
|
||||
bool enabled;
|
||||
|
||||
Filter({
|
||||
required this.url,
|
||||
|
@ -69,3 +69,13 @@ class Filter {
|
|||
"enabled": enabled,
|
||||
};
|
||||
}
|
||||
|
||||
class ProcessedList {
|
||||
final Filter list;
|
||||
final bool successful;
|
||||
|
||||
const ProcessedList({
|
||||
required this.list,
|
||||
required this.successful
|
||||
});
|
||||
}
|
|
@ -52,6 +52,8 @@ class AppConfigProvider with ChangeNotifier {
|
|||
|
||||
int _combinedChartHome = 0;
|
||||
|
||||
int _showTopItemsChart = 0;
|
||||
|
||||
String? _doNotRememberVersion;
|
||||
|
||||
GitHubRelease? _appUpdatesAvailable;
|
||||
|
@ -168,6 +170,10 @@ class AppConfigProvider with ChangeNotifier {
|
|||
return _hideServerAddress == 1 ? true : false;
|
||||
}
|
||||
|
||||
bool get showTopItemsChart {
|
||||
return _showTopItemsChart == 1 ? true : false;
|
||||
}
|
||||
|
||||
void setDbInstance(Database db) {
|
||||
_dbInstance = db;
|
||||
}
|
||||
|
@ -402,6 +408,22 @@ class AppConfigProvider with ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
Future<bool> setShowTopItemsChart(bool value) async {
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'showTopItemsChart',
|
||||
value: value == true ? 1 : 0
|
||||
);
|
||||
if (updated == true) {
|
||||
_showTopItemsChart = value == true ? 1 : 0;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<bool> setDoNotRememberVersion(String value) async {
|
||||
final updated = await updateConfigQuery(
|
||||
|
@ -424,9 +446,10 @@ class AppConfigProvider with ChangeNotifier {
|
|||
_showIpLogs = dbData['showIpLogs'] ?? 0;
|
||||
_combinedChartHome = dbData['combinedChart'] ?? 0;
|
||||
_hideServerAddress = dbData['hideServerAddress'];
|
||||
_showTopItemsChart = dbData['showTopItemsChart'];
|
||||
if (dbData['homeTopItemsOrder'] != null) {
|
||||
try {
|
||||
_homeTopItemsOrder = List<HomeTopItems>.from(
|
||||
final itemsOrder = List<HomeTopItems>.from(
|
||||
List<String>.from(jsonDecode(dbData['homeTopItemsOrder'])).map((e) {
|
||||
switch (e) {
|
||||
case 'queriedDomains':
|
||||
|
@ -438,11 +461,22 @@ class AppConfigProvider with ChangeNotifier {
|
|||
case 'recurrentClients':
|
||||
return HomeTopItems.recurrentClients;
|
||||
|
||||
case 'topUpstreams':
|
||||
return HomeTopItems.topUpstreams;
|
||||
|
||||
case 'avgUpstreamResponseTime':
|
||||
return HomeTopItems.avgUpstreamResponseTime;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}).where((e) => e != null).toList()
|
||||
);
|
||||
final missingItems = homeTopItemsDefaultOrder.where((e) => !itemsOrder.contains(e));
|
||||
_homeTopItemsOrder = [
|
||||
...itemsOrder,
|
||||
...missingItems
|
||||
];
|
||||
} catch (e) {
|
||||
Sentry.captureException(e);
|
||||
_homeTopItemsOrder = homeTopItemsDefaultOrder;
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adguard_home_manager/services/api_client.dart';
|
||||
import 'package:adguard_home_manager/models/clients.dart';
|
||||
import 'package:adguard_home_manager/functions/compare_versions.dart';
|
||||
import 'package:adguard_home_manager/functions/maps_fns.dart';
|
||||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||
import 'package:adguard_home_manager/models/clients_allowed_blocked.dart';
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
|
||||
enum AccessSettingsList { allowed, disallowed, domains }
|
||||
|
||||
class ClientsProvider with ChangeNotifier {
|
||||
ServersProvider? _serversProvider;
|
||||
StatusProvider? _statusProvider;
|
||||
|
||||
update(ServersProvider? servers, StatusProvider? status) {
|
||||
_serversProvider = servers;
|
||||
_statusProvider = status;
|
||||
}
|
||||
|
||||
LoadStatus _loadStatus = LoadStatus.loading;
|
||||
|
@ -103,9 +103,9 @@ class ClientsProvider with ChangeNotifier {
|
|||
if (updateLoading == true) {
|
||||
_loadStatus = LoadStatus.loading;
|
||||
}
|
||||
final result = await _serversProvider!.apiClient!.getClients();
|
||||
if (result['result'] == 'success') {
|
||||
setClientsData(result['data'], false);
|
||||
final result = await _serversProvider!.apiClient2!.getClients();
|
||||
if (result.successful == true) {
|
||||
setClientsData(result.content as Clients, false);
|
||||
_loadStatus = LoadStatus.loaded;
|
||||
notifyListeners();
|
||||
return true;
|
||||
|
@ -120,9 +120,9 @@ class ClientsProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> deleteClient(Client client) async {
|
||||
final result = await _serversProvider!.apiClient!.postDeleteClient(name: client.name);
|
||||
final result = await _serversProvider!.apiClient2!.postDeleteClient(name: client.name);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
Clients clientsData = clients!;
|
||||
clientsData.clients = clientsData.clients.where((c) => c.name != client.name).toList();
|
||||
setClientsData(clientsData, false);
|
||||
|
@ -136,20 +136,14 @@ class ClientsProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> editClient(Client client) async {
|
||||
final result = await _serversProvider!.apiClient!.postUpdateClient(
|
||||
final result = await _serversProvider!.apiClient2!.postUpdateClient(
|
||||
data: {
|
||||
'name': client.name,
|
||||
'data': serverVersionIsAhead(
|
||||
currentVersion: _statusProvider!.serverStatus!.serverVersion,
|
||||
referenceVersion: 'v0.107.28',
|
||||
referenceVersionBeta: 'v0.108.0-b.33'
|
||||
) == false
|
||||
? removePropFromMap(client.toJson(), 'safesearch_enabled')
|
||||
: removePropFromMap(client.toJson(), 'safe_search')
|
||||
'data': removePropFromMap(client.toJson(), 'safe_search')
|
||||
}
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
Clients clientsData = clients!;
|
||||
clientsData.clients = clientsData.clients.map((e) {
|
||||
if (e.name == client.name) {
|
||||
|
@ -171,17 +165,11 @@ class ClientsProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> addClient(Client client) async {
|
||||
final result = await _serversProvider!.apiClient!.postAddClient(
|
||||
data: serverVersionIsAhead(
|
||||
currentVersion: _statusProvider!.serverStatus!.serverVersion,
|
||||
referenceVersion: 'v0.107.28',
|
||||
referenceVersionBeta: 'v0.108.0-b.33'
|
||||
) == false
|
||||
? removePropFromMap(client.toJson(), 'safesearch_enabled')
|
||||
: removePropFromMap(client.toJson(), 'safe_search')
|
||||
final result = await _serversProvider!.apiClient2!.postAddClient(
|
||||
data: removePropFromMap(client.toJson(), 'safe_search')
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
Clients clientsData = clients!;
|
||||
clientsData.clients.add(client);
|
||||
setClientsData(clientsData, false);
|
||||
|
@ -195,91 +183,105 @@ class ClientsProvider with ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> addClientList(String item, String type) async {
|
||||
Future<ApiResponse> addClientList(String item, AccessSettingsList type) async {
|
||||
Map<String, List<String>> body = {
|
||||
"allowed_clients": clients!.clientsAllowedBlocked?.allowedClients ?? [],
|
||||
"disallowed_clients": clients!.clientsAllowedBlocked?.disallowedClients ?? [],
|
||||
"blocked_hosts": clients!.clientsAllowedBlocked?.blockedHosts ?? [],
|
||||
};
|
||||
|
||||
if (type == 'allowed') {
|
||||
if (body['allowed_clients']!.contains(item)) {
|
||||
body['allowed_clients'] = body['allowed_clients']!.where((e) => e != item).toList();
|
||||
}
|
||||
else if (body['disallowed_clients']!.contains(item)) {
|
||||
body['disallowed_clients'] = body['disallowed_clients']!.where((e) => e != item).toList();
|
||||
}
|
||||
else if (body['blocked_hosts']!.contains(item)) {
|
||||
body['blocked_hosts'] = body['blocked_hosts']!.where((e) => e != item).toList();
|
||||
}
|
||||
|
||||
if (type == AccessSettingsList.allowed) {
|
||||
body['allowed_clients']!.add(item);
|
||||
}
|
||||
else if (type == 'disallowed') {
|
||||
else if (type == AccessSettingsList.disallowed) {
|
||||
body['disallowed_clients']!.add(item);
|
||||
}
|
||||
else if (type == 'domains') {
|
||||
else if (type == AccessSettingsList.domains) {
|
||||
body['blocked_hosts']!.add(item);
|
||||
}
|
||||
|
||||
final result = await _serversProvider!.apiClient!.requestAllowedBlockedClientsHosts(body);
|
||||
final result = await _serversProvider!.apiClient2!.requestAllowedBlockedClientsHosts(
|
||||
body: body
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
_clients?.clientsAllowedBlocked = ClientsAllowedBlocked(
|
||||
allowedClients: body['allowed_clients'] ?? [],
|
||||
disallowedClients: body['disallowed_clients'] ?? [],
|
||||
blockedHosts: body['blocked_hosts'] ?? [],
|
||||
);
|
||||
notifyListeners();
|
||||
return { 'success': true };
|
||||
return result;
|
||||
}
|
||||
else if (result['result'] == 'error' && result['message'] == 'client_another_list') {
|
||||
else if (result.successful == false && result.content == 'client_another_list') {
|
||||
notifyListeners();
|
||||
return {
|
||||
'success': false,
|
||||
'error': 'client_another_list'
|
||||
};
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
notifyListeners();
|
||||
return {
|
||||
'success': false,
|
||||
'error': null
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> removeClientList(String client, String type) async {
|
||||
AccessSettingsList? checkClientList(String client) {
|
||||
if (_clients!.clientsAllowedBlocked!.allowedClients.contains(client)) {
|
||||
return AccessSettingsList.allowed;
|
||||
}
|
||||
else if (_clients!.clientsAllowedBlocked!.disallowedClients.contains(client)) {
|
||||
return AccessSettingsList.disallowed;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<ApiResponse> removeClientList(String client, AccessSettingsList type) async {
|
||||
Map<String, List<String>> body = {
|
||||
"allowed_clients": clients!.clientsAllowedBlocked?.allowedClients ?? [],
|
||||
"disallowed_clients": clients!.clientsAllowedBlocked?.disallowedClients ?? [],
|
||||
"blocked_hosts": clients!.clientsAllowedBlocked?.blockedHosts ?? [],
|
||||
};
|
||||
|
||||
if (type == 'allowed') {
|
||||
if (type == AccessSettingsList.allowed) {
|
||||
body['allowed_clients'] = body['allowed_clients']!.where((c) => c != client).toList();
|
||||
}
|
||||
else if (type == 'disallowed') {
|
||||
else if (type == AccessSettingsList.disallowed) {
|
||||
body['disallowed_clients'] = body['disallowed_clients']!.where((c) => c != client).toList();
|
||||
}
|
||||
else if (type == 'domains') {
|
||||
else if (type == AccessSettingsList.domains) {
|
||||
body['blocked_hosts'] = body['blocked_hosts']!.where((c) => c != client).toList();
|
||||
}
|
||||
|
||||
final result = await _serversProvider!.apiClient!.requestAllowedBlockedClientsHosts(body);
|
||||
final result = await _serversProvider!.apiClient2!.requestAllowedBlockedClientsHosts(
|
||||
body: body
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
_clients?.clientsAllowedBlocked = ClientsAllowedBlocked(
|
||||
allowedClients: body['allowed_clients'] ?? [],
|
||||
disallowedClients: body['disallowed_clients'] ?? [],
|
||||
blockedHosts: body['blocked_hosts'] ?? [],
|
||||
);
|
||||
notifyListeners();
|
||||
return { 'success': true };
|
||||
return result;
|
||||
}
|
||||
else if (result['result'] == 'error' && result['message'] == 'client_another_list') {
|
||||
else if (result.successful == false && result.content == 'client_another_list') {
|
||||
notifyListeners();
|
||||
return {
|
||||
'success': false,
|
||||
'error': 'client_another_list'
|
||||
};
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
notifyListeners();
|
||||
return {
|
||||
'success': false,
|
||||
'error': null
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adguard_home_manager/services/api_client.dart';
|
||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/models/dhcp.dart';
|
||||
|
@ -41,9 +42,9 @@ class DhcpProvider with ChangeNotifier {
|
|||
_loadStatus = LoadStatus.loading;
|
||||
notifyListeners();
|
||||
}
|
||||
final result = await _serversProvider!.apiClient!.getDhcpData();
|
||||
if (result['result'] == 'success') {
|
||||
_dhcp = result['data'];
|
||||
final result = await _serversProvider!.apiClient2!.getDhcpData();
|
||||
if (result.successful == true) {
|
||||
_dhcp = result.content as DhcpModel;
|
||||
_loadStatus = LoadStatus.loaded;
|
||||
notifyListeners();
|
||||
return true;
|
||||
|
@ -58,7 +59,7 @@ class DhcpProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> deleteLease(Lease lease) async {
|
||||
final result = await _serversProvider!.apiClient!.deleteStaticLease(
|
||||
final result = await _serversProvider!.apiClient2!.deleteStaticLease(
|
||||
data: {
|
||||
"mac": lease.mac,
|
||||
"ip": lease.ip,
|
||||
|
@ -66,9 +67,9 @@ class DhcpProvider with ChangeNotifier {
|
|||
}
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
DhcpModel data = dhcp!;
|
||||
data.dhcpStatus.staticLeases = data.dhcpStatus.staticLeases.where((l) => l.mac != lease.mac).toList();
|
||||
data.dhcpStatus!.staticLeases = data.dhcpStatus!.staticLeases.where((l) => l.mac != lease.mac).toList();
|
||||
setDhcpData(data);
|
||||
return true;
|
||||
}
|
||||
|
@ -78,8 +79,8 @@ class DhcpProvider with ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> createLease(Lease lease) async {
|
||||
final result = await _serversProvider!.apiClient!.createStaticLease(
|
||||
Future<ApiResponse> createLease(Lease lease) async {
|
||||
final result = await _serversProvider!.apiClient2!.createStaticLease(
|
||||
data: {
|
||||
"mac": lease.mac,
|
||||
"ip": lease.ip,
|
||||
|
@ -87,29 +88,14 @@ class DhcpProvider with ChangeNotifier {
|
|||
}
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
DhcpModel data = dhcp!;
|
||||
data.dhcpStatus.staticLeases.add(lease);
|
||||
data.dhcpStatus!.staticLeases.add(lease);
|
||||
setDhcpData(data);
|
||||
return { 'success': true };
|
||||
}
|
||||
else if (result['result'] == 'error' && result['message'] == 'already_exists' ) {
|
||||
return {
|
||||
'success': false,
|
||||
'error': 'already_exists'
|
||||
};
|
||||
}
|
||||
else if (result['result'] == 'error' && result['message'] == 'server_not_configured' ) {
|
||||
return {
|
||||
'success': false,
|
||||
'error': 'server_not_configured'
|
||||
};
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return {
|
||||
'success': false,
|
||||
'error': null
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adguard_home_manager/services/api_client.dart';
|
||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/models/dns_info.dart';
|
||||
|
@ -41,10 +42,10 @@ class DnsProvider with ChangeNotifier {
|
|||
_loadStatus = LoadStatus.loading;
|
||||
}
|
||||
|
||||
final result = await _serversProvider!.apiClient!.getDnsInfo();
|
||||
final result = await _serversProvider!.apiClient2!.getDnsInfo();
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
_dnsInfo = result['data'];
|
||||
if (result.successful == true) {
|
||||
_dnsInfo = result.content as DnsInfo;
|
||||
_loadStatus = LoadStatus.loaded;
|
||||
notifyListeners();
|
||||
return true;
|
||||
|
@ -58,12 +59,12 @@ class DnsProvider with ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> savePrivateReverseServersConfig(Map<String, dynamic> value) async {
|
||||
final result = await _serversProvider!.apiClient!.setDnsConfig(
|
||||
Future<ApiResponse> savePrivateReverseServersConfig(Map<String, dynamic> value) async {
|
||||
final result = await _serversProvider!.apiClient2!.setDnsConfig(
|
||||
data: value
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
DnsInfo data = dnsInfo!;
|
||||
if (value['local_ptr_upstreams'] != null) {
|
||||
data.localPtrUpstreams = value['local_ptr_upstreams'];
|
||||
|
@ -71,107 +72,87 @@ class DnsProvider with ChangeNotifier {
|
|||
data.usePrivatePtrResolvers = value['use_private_ptr_resolvers'];
|
||||
data.resolveClients = value['resolve_clients'];
|
||||
setDnsInfoData(data);
|
||||
return { 'success': true };
|
||||
}
|
||||
else if (result['log'] != null && result['log'].statusCode == '400') {
|
||||
return {
|
||||
'success': false,
|
||||
'error': 400
|
||||
};
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return {
|
||||
'success': false,
|
||||
'error': null
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> saveUpstreamDnsConfig(Map<String, dynamic> value) async {
|
||||
final result = await _serversProvider!.apiClient!.setDnsConfig(
|
||||
Future<ApiResponse> saveUpstreamDnsConfig(Map<String, dynamic> value) async {
|
||||
final result = await _serversProvider!.apiClient2!.setDnsConfig(
|
||||
data: value
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
DnsInfo data = dnsInfo!;
|
||||
data.upstreamDns = List<String>.from(value['upstream_dns']);
|
||||
data.upstreamMode = value['upstream_mode'];
|
||||
setDnsInfoData(data);
|
||||
return { 'success': true };
|
||||
}
|
||||
else if (result['log'] != null && result['log'].statusCode == '400') {
|
||||
return {
|
||||
'success': false,
|
||||
'error': 400
|
||||
};
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return {
|
||||
'success': false,
|
||||
'error': null
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> saveBootstrapDnsConfig(Map<String, dynamic> value) async {
|
||||
final result = await _serversProvider!.apiClient!.setDnsConfig(
|
||||
Future<ApiResponse> saveBootstrapDnsConfig(Map<String, dynamic> value) async {
|
||||
final result = await _serversProvider!.apiClient2!.setDnsConfig(
|
||||
data: value
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
DnsInfo data = dnsInfo!;
|
||||
data.bootstrapDns = List<String>.from(value['bootstrap_dns']);
|
||||
setDnsInfoData(data);
|
||||
return { 'success': true };
|
||||
}
|
||||
else if (result['log'] != null && result['log'].statusCode == '400') {
|
||||
return {
|
||||
'success': false,
|
||||
'error': 400
|
||||
};
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return {
|
||||
'success': false,
|
||||
'error': null
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> saveCacheCacheConfig(Map<String, dynamic> value) async {
|
||||
final result = await _serversProvider!.apiClient!.setDnsConfig(
|
||||
Future<ApiResponse> saveFallbackDnsConfig(Map<String, dynamic> value) async {
|
||||
final result = await _serversProvider!.apiClient2!.setDnsConfig(
|
||||
data: value
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
DnsInfo data = dnsInfo!;
|
||||
data.fallbackDns = List<String>.from(value['fallback_dns']);
|
||||
setDnsInfoData(data);
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Future<ApiResponse> saveCacheCacheConfig(Map<String, dynamic> value) async {
|
||||
final result = await _serversProvider!.apiClient2!.setDnsConfig(
|
||||
data: value
|
||||
);
|
||||
|
||||
if (result.successful == true) {
|
||||
DnsInfo data = dnsInfo!;
|
||||
data.cacheSize = value['cache_size'];
|
||||
data.cacheTtlMin = value['cache_ttl_min'];
|
||||
data.cacheTtlMax = value['cache_ttl_max'];
|
||||
data.cacheOptimistic = value['cache_optimistic'];
|
||||
setDnsInfoData(data);
|
||||
return { 'success': true };
|
||||
}
|
||||
else if (result['log'] != null && result['log'].statusCode == '400') {
|
||||
return {
|
||||
'success': false,
|
||||
'error': 400
|
||||
};
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return {
|
||||
'success': false,
|
||||
'error': null
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> saveDnsServerConfig(Map<String, dynamic> value) async {
|
||||
final result = await _serversProvider!.apiClient!.setDnsConfig(
|
||||
Future<ApiResponse> saveDnsServerConfig(Map<String, dynamic> value) async {
|
||||
final result = await _serversProvider!.apiClient2!.setDnsConfig(
|
||||
data: value
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
DnsInfo data = dnsInfo!;
|
||||
data.ratelimit = value['ratelimit'];
|
||||
data.ednsCsEnabled = value['edns_cs_enabled'];
|
||||
|
@ -180,20 +161,12 @@ class DnsProvider with ChangeNotifier {
|
|||
data.blockingMode = value['blocking_mode'];
|
||||
data.blockingIpv4 = value['blocking_ipv4'];
|
||||
data.blockingIpv6 = value['blocking_ipv6'];
|
||||
data.blockedResponseTtl = value['blocked_response_ttl'];
|
||||
setDnsInfoData(data);
|
||||
return { 'success': true };
|
||||
}
|
||||
else if (result['log'] != null && result['log'].statusCode == '400') {
|
||||
return {
|
||||
'success': false,
|
||||
'error': 400
|
||||
};
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return {
|
||||
'success': false,
|
||||
'error': null
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -88,10 +88,10 @@ class FilteringProvider with ChangeNotifier {
|
|||
_blockedServicesLoadStatus = LoadStatus.loading;
|
||||
if (showLoader == true) notifyListeners();
|
||||
|
||||
final result = await _serversProvider!.apiClient!.getBlockedServices();
|
||||
if (result['result'] == 'success') {
|
||||
final result = await _serversProvider!.apiClient2!.getBlockedServices();
|
||||
if (result.successful == true) {
|
||||
_blockedServicesLoadStatus = LoadStatus.loaded;
|
||||
_blockedServicesList = BlockedServices(services: result['data']);
|
||||
_blockedServicesList = BlockedServices(services: result.content as List<BlockedService>);
|
||||
|
||||
notifyListeners();
|
||||
return true;
|
||||
|
@ -112,9 +112,9 @@ class FilteringProvider with ChangeNotifier {
|
|||
_loadStatus = LoadStatus.loading;
|
||||
}
|
||||
|
||||
final result = await _serversProvider!.apiClient!.getFiltering();
|
||||
if (result['result'] == 'success') {
|
||||
_filtering = result['data'];
|
||||
final result = await _serversProvider!.apiClient2!.getFiltering();
|
||||
if (result.successful == true) {
|
||||
_filtering = result.content as Filtering;
|
||||
_loadStatus = LoadStatus.loaded;
|
||||
notifyListeners();
|
||||
return true;
|
||||
|
@ -127,15 +127,16 @@ class FilteringProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<Map<String, dynamic>> updateLists() async {
|
||||
final result = await _serversProvider!.apiClient!.updateLists();
|
||||
if (result['result'] == 'success') {
|
||||
final result2 = await _serversProvider!.apiClient!.getFiltering();
|
||||
if (result2['result'] == 'success') {
|
||||
_filtering = result2['data'];
|
||||
final result = await _serversProvider!.apiClient2!.updateLists();
|
||||
if (result.successful == true) {
|
||||
final result2 = await _serversProvider!.apiClient2!.getFiltering();
|
||||
if (result2.successful == true) {
|
||||
_filtering = result2.content as Filtering;
|
||||
notifyListeners();
|
||||
print(result.content);
|
||||
return {
|
||||
"success": true,
|
||||
"data": result['data']
|
||||
"data": result.content
|
||||
};
|
||||
}
|
||||
else {
|
||||
|
@ -151,10 +152,10 @@ class FilteringProvider with ChangeNotifier {
|
|||
|
||||
Future<bool> enableDisableFiltering() async {
|
||||
final newValue = !_statusProvider!.serverStatus!.filteringEnabled;
|
||||
final result = await _serversProvider!.apiClient!.updateFiltering(
|
||||
final result = await _serversProvider!.apiClient2!.updateFiltering(
|
||||
enable: newValue
|
||||
);
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
setFilteringProtectionStatus(newValue, false);
|
||||
notifyListeners();
|
||||
return true;
|
||||
|
@ -166,13 +167,13 @@ class FilteringProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> changeUpdateFrequency(int value) async {
|
||||
final result = await _serversProvider!.apiClient!.requestChangeUpdateFrequency(
|
||||
final result = await _serversProvider!.apiClient2!.requestChangeUpdateFrequency(
|
||||
data: {
|
||||
"enabled": filtering!.enabled,
|
||||
"interval": value
|
||||
}
|
||||
);
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
setFiltersUpdateFrequency(value);
|
||||
return true;
|
||||
}
|
||||
|
@ -185,9 +186,9 @@ class FilteringProvider with ChangeNotifier {
|
|||
Future<bool> removeCustomRule(String rule) async {
|
||||
final List<String> newRules = filtering!.userRules.where((r) => r != rule).toList();
|
||||
|
||||
final result = await _serversProvider!.apiClient!.setCustomRules(rules: newRules);
|
||||
final result = await _serversProvider!.apiClient2!.setCustomRules(rules: newRules);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
Filtering filteringData = filtering!;
|
||||
filteringData.userRules = newRules;
|
||||
_filtering = filteringData;
|
||||
|
@ -205,18 +206,18 @@ class FilteringProvider with ChangeNotifier {
|
|||
required String listUrl,
|
||||
required String type
|
||||
}) async {
|
||||
final result1 = await _serversProvider!.apiClient!.deleteFilterList(
|
||||
final result1 = await _serversProvider!.apiClient2!.deleteFilterList(
|
||||
data: {
|
||||
"url": listUrl,
|
||||
"whitelist": type == 'whitelist' ? true : false
|
||||
}
|
||||
);
|
||||
|
||||
if (result1['result'] == 'success') {
|
||||
final result2 = await _serversProvider!.apiClient!.getFiltering();
|
||||
if (result1.successful == true) {
|
||||
final result2 = await _serversProvider!.apiClient2!.getFiltering();
|
||||
|
||||
if (result2['result'] == 'success') {
|
||||
_filtering = result2['data'];
|
||||
if (result2.successful == true) {
|
||||
_filtering = result2.content as Filtering;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
@ -236,7 +237,7 @@ class FilteringProvider with ChangeNotifier {
|
|||
required String type,
|
||||
required FilteringListActions action
|
||||
}) async {
|
||||
final result1 = await _serversProvider!.apiClient!.updateFilterList(
|
||||
final result1 = await _serversProvider!.apiClient2!.updateFilterList(
|
||||
data: {
|
||||
"data": {
|
||||
"enabled": action == FilteringListActions.disable || action == FilteringListActions.enable
|
||||
|
@ -250,11 +251,11 @@ class FilteringProvider with ChangeNotifier {
|
|||
}
|
||||
);
|
||||
|
||||
if (result1['result'] == 'success') {
|
||||
final result2 = await _serversProvider!.apiClient!.getFiltering();
|
||||
if (result1.successful == true) {
|
||||
final result2 = await _serversProvider!.apiClient2!.getFiltering();
|
||||
|
||||
if (result2['result'] == 'success') {
|
||||
_filtering = result2['data'];
|
||||
if (result2.successful == true) {
|
||||
_filtering = result2.content as Filtering;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
@ -273,9 +274,9 @@ class FilteringProvider with ChangeNotifier {
|
|||
final List<String> newRules = filtering!.userRules;
|
||||
newRules.add(rule);
|
||||
|
||||
final result = await _serversProvider!.apiClient!.setCustomRules(rules: newRules);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
final result = await _serversProvider!.apiClient2!.setCustomRules(rules: newRules);
|
||||
|
||||
if (result.successful == true) {
|
||||
Filtering filteringData = filtering!;
|
||||
filteringData.userRules = newRules;
|
||||
_filtering = filteringData;
|
||||
|
@ -287,8 +288,9 @@ class FilteringProvider with ChangeNotifier {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> addList({required String name, required String url, required String type}) async {
|
||||
final result1 = await _serversProvider!.apiClient!.addFilteringList(
|
||||
final result1 = await _serversProvider!.apiClient2!.addFilteringList(
|
||||
data: {
|
||||
'name': name,
|
||||
'url': url,
|
||||
|
@ -296,13 +298,13 @@ class FilteringProvider with ChangeNotifier {
|
|||
}
|
||||
);
|
||||
|
||||
if (result1['result'] == 'success') {
|
||||
if (result1['data'].toString().contains("OK")) {
|
||||
final result2 = await _serversProvider!.apiClient!.getFiltering();
|
||||
final items = result1['data'].toString().split(' ')[1];
|
||||
if (result1.successful == true) {
|
||||
if (result1.content.toString().contains("OK")) {
|
||||
final result2 = await _serversProvider!.apiClient2!.getFiltering();
|
||||
final items = result1.content.toString().split(' ')[1];
|
||||
|
||||
if (result2['result'] == 'success') {
|
||||
_filtering = result2['data'];
|
||||
if (result2.successful == true) {
|
||||
_filtering = result2.content as Filtering;
|
||||
notifyListeners();
|
||||
return {
|
||||
'success': true,
|
||||
|
@ -325,14 +327,14 @@ class FilteringProvider with ChangeNotifier {
|
|||
};
|
||||
}
|
||||
}
|
||||
else if (result1['result'] == 'error' && result1['log'].statusCode == '400' && result1['log'].resBody.toString().contains("Couldn't fetch filter from url")) {
|
||||
else if (result1.successful == false && result1.statusCode == 400 && result1.content.toString().contains("data is HTML, not plain text")) {
|
||||
notifyListeners();
|
||||
return {
|
||||
'success': false,
|
||||
'error': 'invalid_url'
|
||||
};
|
||||
}
|
||||
else if (result1['result'] == 'error' && result1['log'].statusCode == '400' && result1['log'].resBody.toString().contains('Filter URL already added')) {
|
||||
else if (result1.successful == false && result1.statusCode == 400 && result1.content.toString().contains('url already exists')) {
|
||||
notifyListeners();
|
||||
return {
|
||||
'success': false,
|
||||
|
@ -355,9 +357,9 @@ class FilteringProvider with ChangeNotifier {
|
|||
_blockedServicesLoadStatus = LoadStatus.loading;
|
||||
}
|
||||
|
||||
final result = await _serversProvider!.apiClient!.getBlockedServices();
|
||||
if (result['result'] == 'success') {
|
||||
_blockedServicesList = BlockedServices(services: result['data']);
|
||||
final result = await _serversProvider!.apiClient2!.getBlockedServices();
|
||||
if (result.successful == true) {
|
||||
_blockedServicesList = BlockedServices(services: result.content as List<BlockedService>);
|
||||
_blockedServicesLoadStatus = LoadStatus.loaded;
|
||||
|
||||
notifyListeners();
|
||||
|
@ -371,11 +373,11 @@ class FilteringProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> updateBlockedServices(List<String> values) async {
|
||||
final result = await _serversProvider!.apiClient!.setBlockedServices(
|
||||
final result = await _serversProvider!.apiClient2!.setBlockedServices(
|
||||
data: values
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
setBlockedServices(values);
|
||||
return true;
|
||||
}
|
||||
|
@ -384,4 +386,75 @@ class FilteringProvider with ChangeNotifier {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<ProcessedList>> deleteMultipleLists({
|
||||
required List<Filter> blacklists,
|
||||
required List<Filter> whitelists
|
||||
}) async {
|
||||
Future<ProcessedList> deleteList({
|
||||
required Filter list,
|
||||
required bool isWhitelist,
|
||||
}) async {
|
||||
final result = await _serversProvider!.apiClient2!.deleteFilterList(
|
||||
data: {
|
||||
"url": list.url,
|
||||
"whitelist": isWhitelist
|
||||
}
|
||||
);
|
||||
if (result.successful == true) {
|
||||
return ProcessedList(list: list, successful: true);
|
||||
}
|
||||
else {
|
||||
return ProcessedList(list: list, successful: false);
|
||||
}
|
||||
}
|
||||
|
||||
final resultWhitelists = await Future.wait(whitelists.map((e) => deleteList(list: e, isWhitelist: true)));
|
||||
final resultBlacklists = await Future.wait(blacklists.map((e) => deleteList(list: e, isWhitelist: false)));
|
||||
|
||||
await fetchFilters();
|
||||
|
||||
return [
|
||||
...resultWhitelists,
|
||||
...resultBlacklists,
|
||||
];
|
||||
}
|
||||
|
||||
Future<List<ProcessedList>> enableDisableMultipleLists({
|
||||
required List<Filter> blacklists,
|
||||
required List<Filter> whitelists
|
||||
}) async {
|
||||
Future<ProcessedList> enableDisableList({
|
||||
required Filter list,
|
||||
required bool isWhitelist,
|
||||
}) async {
|
||||
final result = await _serversProvider!.apiClient2!.updateFilterList(
|
||||
data: {
|
||||
"data": {
|
||||
"enabled": !list.enabled,
|
||||
"name": list.name,
|
||||
"url": list.url
|
||||
},
|
||||
"url": list.url,
|
||||
"whitelist": isWhitelist
|
||||
}
|
||||
);
|
||||
if (result.successful == true) {
|
||||
return ProcessedList(list: list, successful: true);
|
||||
}
|
||||
else {
|
||||
return ProcessedList(list: list, successful: false);
|
||||
}
|
||||
}
|
||||
|
||||
final resultWhitelists = await Future.wait(whitelists.map((e) => enableDisableList(list: e, isWhitelist: true)));
|
||||
final resultBlacklists = await Future.wait(blacklists.map((e) => enableDisableList(list: e, isWhitelist: false)));
|
||||
|
||||
await fetchFilters();
|
||||
|
||||
return [
|
||||
...resultWhitelists,
|
||||
...resultBlacklists,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -114,9 +114,15 @@ class LogsProvider with ChangeNotifier {
|
|||
_offset = value;
|
||||
}
|
||||
|
||||
void setSelectedResultStatus(String value) {
|
||||
void setSelectedResultStatus({
|
||||
required String value,
|
||||
bool? refetch
|
||||
}) {
|
||||
_selectedResultStatus = value;
|
||||
notifyListeners();
|
||||
if (refetch = true) {
|
||||
filterLogs();
|
||||
}
|
||||
}
|
||||
|
||||
void setSearchText(String? value) {
|
||||
|
@ -153,7 +159,7 @@ class LogsProvider with ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
final result = await _serversProvider!.apiClient!.getLogs(
|
||||
final result = await _serversProvider!.apiClient2!.getLogs(
|
||||
count: logsQuantity,
|
||||
offset: offst,
|
||||
olderThan: logsOlderThan,
|
||||
|
@ -166,11 +172,11 @@ class LogsProvider with ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
_offset = inOffset != null ? inOffset+logsQuantity : offset+logsQuantity;
|
||||
if (loadingMore != null && loadingMore == true && logsData != null) {
|
||||
LogsData newLogsData = result['data'];
|
||||
newLogsData.data = [...logsData!.data, ...result['data'].data];
|
||||
LogsData newLogsData = result.content;
|
||||
newLogsData.data = [...logsData!.data, ...(result.content as LogsData).data];
|
||||
if (appliedFilters.clients != null) {
|
||||
newLogsData.data = newLogsData.data.where(
|
||||
(item) => appliedFilters.clients!.contains(item.client)
|
||||
|
@ -179,7 +185,7 @@ class LogsProvider with ChangeNotifier {
|
|||
_logsData = newLogsData;
|
||||
}
|
||||
else {
|
||||
LogsData newLogsData = result['data'];
|
||||
LogsData newLogsData = result.content;
|
||||
if (appliedFilters.clients != null) {
|
||||
newLogsData.data = newLogsData.data.where(
|
||||
(item) => appliedFilters.clients!.contains(item.client)
|
||||
|
@ -204,7 +210,7 @@ class LogsProvider with ChangeNotifier {
|
|||
|
||||
resetFilters();
|
||||
|
||||
final result = await _serversProvider!.apiClient!.getLogs(
|
||||
final result = await _serversProvider!.apiClient2!.getLogs(
|
||||
count: logsQuantity
|
||||
);
|
||||
|
||||
|
@ -214,8 +220,8 @@ class LogsProvider with ChangeNotifier {
|
|||
clients: null
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
_logsData = result['data'];
|
||||
if (result.successful == true) {
|
||||
_logsData = result.content as LogsData;
|
||||
_loadStatus = LoadStatus.loaded;
|
||||
notifyListeners();
|
||||
return true;
|
||||
|
@ -233,7 +239,7 @@ class LogsProvider with ChangeNotifier {
|
|||
|
||||
setOffset(0);
|
||||
|
||||
final result = await _serversProvider!.apiClient!.getLogs(
|
||||
final result = await _serversProvider!.apiClient2!.getLogs(
|
||||
count: logsQuantity,
|
||||
olderThan: logsOlderThan,
|
||||
responseStatus: selectedResultStatus,
|
||||
|
@ -246,8 +252,8 @@ class LogsProvider with ChangeNotifier {
|
|||
clients: selectedClients
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
LogsData newLogsData = result['data'];
|
||||
if (result.successful == true) {
|
||||
LogsData newLogsData = result.content as LogsData;
|
||||
if (appliedFilters.clients != null) {
|
||||
newLogsData.data = newLogsData.data.where(
|
||||
(item) => appliedFilters.clients!.contains(item.client)
|
||||
|
|
|
@ -35,14 +35,14 @@ class RewriteRulesProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> addDnsRewrite(RewriteRules rule) async {
|
||||
final result = await _serversProvider!.apiClient!.addDnsRewriteRule(
|
||||
final result = await _serversProvider!.apiClient2!.addDnsRewriteRule(
|
||||
data: {
|
||||
"domain": rule.domain,
|
||||
"answer": rule.answer
|
||||
}
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
List<RewriteRules> data = rewriteRules!;
|
||||
data.add(rule);
|
||||
setRewriteRulesData(data);
|
||||
|
@ -55,7 +55,7 @@ class RewriteRulesProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> editDnsRewrite(RewriteRules newRule, RewriteRules oldRule) async {
|
||||
final result = await _serversProvider!.apiClient!.updateRewriteRule(
|
||||
final result = await _serversProvider!.apiClient2!.updateRewriteRule(
|
||||
body: {
|
||||
"target": {
|
||||
"answer": oldRule.answer,
|
||||
|
@ -68,7 +68,7 @@ class RewriteRulesProvider with ChangeNotifier {
|
|||
}
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
List<RewriteRules> data = rewriteRules!;
|
||||
final index = data.indexOf(oldRule);
|
||||
data[index] = newRule;
|
||||
|
@ -82,14 +82,14 @@ class RewriteRulesProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> deleteDnsRewrite(RewriteRules rule) async {
|
||||
final result = await _serversProvider!.apiClient!.deleteDnsRewriteRule(
|
||||
final result = await _serversProvider!.apiClient2!.deleteDnsRewriteRule(
|
||||
data: {
|
||||
"domain": rule.domain,
|
||||
"answer": rule.answer
|
||||
}
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
List<RewriteRules> data = rewriteRules!;
|
||||
data = data.where((item) => item.domain != rule.domain).toList();
|
||||
setRewriteRulesData(data);
|
||||
|
@ -108,10 +108,10 @@ class RewriteRulesProvider with ChangeNotifier {
|
|||
_loadStatus = LoadStatus.loading;
|
||||
}
|
||||
|
||||
final result = await _serversProvider!.apiClient!.getDnsRewriteRules();
|
||||
final result = await _serversProvider!.apiClient2!.getDnsRewriteRules();
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
_rewriteRules = result['data'];
|
||||
if (result.successful == true) {
|
||||
_rewriteRules = result.content as List<RewriteRules>;
|
||||
_loadStatus = LoadStatus.loaded;
|
||||
notifyListeners();
|
||||
return true;
|
||||
|
|
|
@ -3,9 +3,11 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/github_release.dart';
|
||||
import 'package:adguard_home_manager/services/api_client.dart';
|
||||
import 'package:adguard_home_manager/services/external_requests.dart';
|
||||
import 'package:adguard_home_manager/models/server.dart';
|
||||
import 'package:adguard_home_manager/models/update_available.dart';
|
||||
import 'package:adguard_home_manager/services/http_requests.dart';
|
||||
import 'package:adguard_home_manager/functions/conversions.dart';
|
||||
import 'package:adguard_home_manager/services/db/queries.dart';
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
|
@ -15,7 +17,8 @@ class ServersProvider with ChangeNotifier {
|
|||
|
||||
List<Server> _serversList = [];
|
||||
Server? _selectedServer;
|
||||
ApiClient? _apiClient;
|
||||
// ApiClient? _apiClient;
|
||||
ApiClientV2? _apiClient2;
|
||||
|
||||
bool _updatingServer = false;
|
||||
|
||||
|
@ -24,8 +27,12 @@ class ServersProvider with ChangeNotifier {
|
|||
data: null,
|
||||
);
|
||||
|
||||
ApiClient? get apiClient {
|
||||
return _apiClient;
|
||||
// ApiClient? get apiClient {
|
||||
// return _apiClient;
|
||||
// }
|
||||
|
||||
ApiClientV2? get apiClient2 {
|
||||
return _apiClient2;
|
||||
}
|
||||
|
||||
List<Server> get serversList {
|
||||
|
@ -53,7 +60,7 @@ class ServersProvider with ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
void setSelectedServer(Server server) {
|
||||
void setSelectedServer(Server? server) {
|
||||
_selectedServer = server;
|
||||
notifyListeners();
|
||||
}
|
||||
|
@ -70,8 +77,13 @@ class ServersProvider with ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
void setApiClient(ApiClient client) {
|
||||
_apiClient = client;
|
||||
// void setApiClient(ApiClient client) {
|
||||
// _apiClient = client;
|
||||
// notifyListeners();
|
||||
// }
|
||||
|
||||
void setApiClient2(ApiClientV2 client) {
|
||||
_apiClient2 = client;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
@ -141,7 +153,8 @@ class ServersProvider with ChangeNotifier {
|
|||
_serversList = newServers;
|
||||
|
||||
if (selectedServer != null &&server.id == selectedServer!.id) {
|
||||
_apiClient = ApiClient(server: server);
|
||||
// _apiClient = ApiClient(server: server);
|
||||
_apiClient2 = ApiClientV2(server: server);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
|
@ -156,7 +169,7 @@ class ServersProvider with ChangeNotifier {
|
|||
final result = await removeServerQuery(_dbInstance!, server.id);
|
||||
if (result == true) {
|
||||
_selectedServer = null;
|
||||
_apiClient = null;
|
||||
// _apiClient = null;
|
||||
List<Server> newServers = _serversList.where((s) => s.id != server.id).toList();
|
||||
_serversList = newServers;
|
||||
notifyListeners();
|
||||
|
@ -169,14 +182,16 @@ class ServersProvider with ChangeNotifier {
|
|||
|
||||
void checkServerUpdatesAvailable({
|
||||
required Server server,
|
||||
ApiClientV2? apiClient
|
||||
}) async {
|
||||
final client = apiClient ?? _apiClient2;
|
||||
setUpdateAvailableLoadStatus(LoadStatus.loading, true);
|
||||
final result = await _apiClient!.checkServerUpdates();
|
||||
if (result['result'] == 'success') {
|
||||
UpdateAvailableData data = UpdateAvailableData.fromJson(result['data']);
|
||||
final gitHubResult = await _apiClient!.getUpdateChangelog(releaseTag: data.newVersion ?? data.currentVersion);
|
||||
if (gitHubResult['result'] == 'success') {
|
||||
data.changelog = gitHubResult['body'];
|
||||
final result = await client!.checkServerUpdates();
|
||||
if (result.successful == true) {
|
||||
UpdateAvailableData data = UpdateAvailableData.fromJson(result.content);
|
||||
final gitHubResult = await ExternalRequests.getReleaseData(releaseTag: data.newVersion ?? data.currentVersion);
|
||||
if (gitHubResult.successful == true) {
|
||||
data.changelog = (gitHubResult.content as GitHubRelease).body;
|
||||
}
|
||||
setUpdateAvailableData(data);
|
||||
setUpdateAvailableLoadStatus(LoadStatus.loaded, true);
|
||||
|
@ -186,11 +201,12 @@ class ServersProvider with ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
Future initializateServer(Server server) async {
|
||||
final serverStatus = await _apiClient!.getServerStatus();
|
||||
if (serverStatus['result'] == 'success') {
|
||||
Future initializateServer(Server server, /*ApiClient apiClient, */ ApiClientV2 apiClient2) async {
|
||||
final serverStatus = await _apiClient2!.getServerStatus();
|
||||
if (serverStatus.successful == true) {
|
||||
checkServerUpdatesAvailable( // Do not await
|
||||
server: server,
|
||||
apiClient: apiClient2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -222,8 +238,11 @@ class ServersProvider with ChangeNotifier {
|
|||
|
||||
if (defaultServer != null) {
|
||||
_selectedServer = defaultServer;
|
||||
_apiClient = ApiClient(server: defaultServer);
|
||||
initializateServer(defaultServer);
|
||||
// final client = ApiClient(server: defaultServer);
|
||||
final client2 = ApiClientV2(server: defaultServer);
|
||||
// _apiClient = client;
|
||||
_apiClient2 = client2;
|
||||
initializateServer(defaultServer, /*client,*/ client2);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -240,13 +259,13 @@ class ServersProvider with ChangeNotifier {
|
|||
const Duration(seconds: 2),
|
||||
(timer) async {
|
||||
if (_selectedServer != null && _selectedServer == server) {
|
||||
final result = await _apiClient!.checkServerUpdates();
|
||||
if (result['result'] == 'success') {
|
||||
UpdateAvailableData data = UpdateAvailableData.fromJsonUpdate(result['data']);
|
||||
final result = await _apiClient2!.checkServerUpdates();
|
||||
if (result.successful == true) {
|
||||
UpdateAvailableData data = UpdateAvailableData.fromJsonUpdate(result.content);
|
||||
if (data.currentVersion == data.newVersion) {
|
||||
final gitHubResult = await _apiClient!.getUpdateChangelog(releaseTag: data.newVersion ?? data.currentVersion);
|
||||
if (gitHubResult['result'] == 'success') {
|
||||
data.changelog = gitHubResult['body'];
|
||||
final gitHubResult = await ExternalRequests.getReleaseData(releaseTag: data.newVersion ?? data.currentVersion);
|
||||
if (gitHubResult.successful == true) {
|
||||
data.changelog = (gitHubResult.content as GitHubRelease).body;
|
||||
}
|
||||
setUpdateAvailableData(data);
|
||||
timer.cancel();
|
||||
|
|
|
@ -2,11 +2,15 @@ import 'dart:async';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/add_server/unsupported_version_modal.dart';
|
||||
|
||||
import 'package:adguard_home_manager/config/globals.dart';
|
||||
import 'package:adguard_home_manager/config/minimum_server_version.dart';
|
||||
import 'package:adguard_home_manager/functions/compare_versions.dart';
|
||||
import 'package:adguard_home_manager/models/server_status.dart';
|
||||
import 'package:adguard_home_manager/models/filtering_status.dart';
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||
import 'package:adguard_home_manager/functions/compare_versions.dart';
|
||||
import 'package:adguard_home_manager/functions/time_server_disabled.dart';
|
||||
|
||||
class StatusProvider with ChangeNotifier {
|
||||
|
@ -104,7 +108,7 @@ class StatusProvider with ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
Future<dynamic> updateBlocking({
|
||||
Future<bool> updateBlocking({
|
||||
required String block,
|
||||
required bool newStatus,
|
||||
int? time
|
||||
|
@ -114,14 +118,14 @@ class StatusProvider with ChangeNotifier {
|
|||
_protectionsManagementProcess.add('general');
|
||||
notifyListeners();
|
||||
|
||||
final result = await _serversProvider!.apiClient!.updateGeneralProtection(
|
||||
final result = await _serversProvider!.apiClient2!.updateGeneralProtection(
|
||||
enable: newStatus,
|
||||
time: time
|
||||
);
|
||||
|
||||
_protectionsManagementProcess = _protectionsManagementProcess.where((e) => e != 'general').toList();
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
_serverStatus!.generalEnabled = newStatus;
|
||||
if (time != null) {
|
||||
final deadline = generateTimeDeadline(time);
|
||||
|
@ -135,111 +139,80 @@ class StatusProvider with ChangeNotifier {
|
|||
stopCountdown();
|
||||
}
|
||||
notifyListeners();
|
||||
return null;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
notifyListeners();
|
||||
return result['log'];
|
||||
return false;
|
||||
}
|
||||
|
||||
case 'general_legacy':
|
||||
_protectionsManagementProcess.add('general');
|
||||
notifyListeners();
|
||||
|
||||
final result = await _serversProvider!.apiClient!.updateGeneralProtectionLegacy(newStatus);
|
||||
|
||||
_protectionsManagementProcess = _protectionsManagementProcess.where((e) => e != 'general').toList();
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
_serverStatus!.generalEnabled = newStatus;
|
||||
notifyListeners();
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
notifyListeners();
|
||||
return result['log'];
|
||||
}
|
||||
|
||||
|
||||
case 'filtering':
|
||||
_protectionsManagementProcess.add('filtering');
|
||||
notifyListeners();
|
||||
|
||||
final result = await _serversProvider!.apiClient!.updateFiltering(
|
||||
final result = await _serversProvider!.apiClient2!.updateFiltering(
|
||||
enable: newStatus,
|
||||
);
|
||||
|
||||
_protectionsManagementProcess = _protectionsManagementProcess.where((e) => e != 'filtering').toList();
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
_serverStatus!.filteringEnabled = newStatus;
|
||||
notifyListeners();
|
||||
return null;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
|
||||
notifyListeners();
|
||||
return result['log'];
|
||||
return false;
|
||||
}
|
||||
|
||||
case 'safeSearch':
|
||||
_protectionsManagementProcess.add('safeSearch');
|
||||
notifyListeners();
|
||||
|
||||
final result = serverVersionIsAhead(
|
||||
currentVersion: serverStatus!.serverVersion,
|
||||
referenceVersion: 'v0.107.28',
|
||||
referenceVersionBeta: 'v0.108.0-b.33'
|
||||
) == true
|
||||
? await _serversProvider!.apiClient!.updateSafeSearchSettings(body: { 'enabled': newStatus })
|
||||
: await _serversProvider!.apiClient!.updateSafeSearchLegacy(newStatus);
|
||||
final result = await _serversProvider!.apiClient2!.updateSafeSearchSettings(body: { 'enabled': newStatus });
|
||||
|
||||
_protectionsManagementProcess = _protectionsManagementProcess.where((e) => e != 'safeSearch').toList();
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
_serverStatus!.safeSearchEnabled = newStatus;
|
||||
notifyListeners();
|
||||
return null;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
notifyListeners();
|
||||
return result['log'];
|
||||
return false;
|
||||
}
|
||||
|
||||
case 'safeBrowsing':
|
||||
_protectionsManagementProcess.add('safeBrowsing');
|
||||
notifyListeners();
|
||||
|
||||
final result = await _serversProvider!.apiClient!.updateSafeBrowsing(newStatus);
|
||||
final result = await _serversProvider!.apiClient2!.updateSafeBrowsing(enable: newStatus);
|
||||
|
||||
_protectionsManagementProcess = _protectionsManagementProcess.where((e) => e != 'safeBrowsing').toList();
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
_serverStatus!.safeBrowsingEnabled = newStatus;
|
||||
notifyListeners();
|
||||
return null;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
notifyListeners();
|
||||
return result['log'];
|
||||
return false;
|
||||
}
|
||||
|
||||
case 'parentalControl':
|
||||
_protectionsManagementProcess.add('parentalControl');
|
||||
notifyListeners();
|
||||
|
||||
final result = await _serversProvider!.apiClient!.updateParentalControl(newStatus);
|
||||
final result = await _serversProvider!.apiClient2!.updateParentalControl(enable: newStatus);
|
||||
|
||||
_protectionsManagementProcess = _protectionsManagementProcess.where((e) => e != 'parentalControl').toList();
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
_serverStatus!.parentalControlEnabled = newStatus;
|
||||
notifyListeners();
|
||||
return null;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
notifyListeners();
|
||||
return result['log'];
|
||||
return false;
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -252,9 +225,9 @@ class StatusProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> getFilteringRules() async {
|
||||
final result = await _serversProvider!.apiClient!.getFilteringRules();
|
||||
if (result['result'] == 'success') {
|
||||
_filteringStatus = result['data'];
|
||||
final result = await _serversProvider!.apiClient2!.getFilteringRules();
|
||||
if (result.successful == true) {
|
||||
_filteringStatus = result.content as FilteringStatus;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
@ -264,19 +237,39 @@ class StatusProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> getServerStatus({
|
||||
bool? withLoadingIndicator
|
||||
bool? withLoadingIndicator = true,
|
||||
bool? overrideCheckServerVersion
|
||||
}) async {
|
||||
if (withLoadingIndicator == true) {
|
||||
_loadStatus = LoadStatus.loading;
|
||||
}
|
||||
|
||||
final result = await _serversProvider!.apiClient!.getServerStatus();
|
||||
if (result['result'] == 'success') {
|
||||
final result = await _serversProvider!.apiClient2!.getServerStatus();
|
||||
if (result.successful == true) {
|
||||
final status = result.content as ServerStatus;
|
||||
setServerStatusData(
|
||||
data: result['data']
|
||||
data: status
|
||||
);
|
||||
_loadStatus = LoadStatus.loaded;
|
||||
notifyListeners();
|
||||
|
||||
// Check server version and launch modal if not valid
|
||||
final validVersion = serverVersionIsAhead(
|
||||
currentVersion: status.serverVersion,
|
||||
referenceVersion: MinimumServerVersion.stable,
|
||||
referenceVersionBeta: MinimumServerVersion.beta
|
||||
);
|
||||
if (validVersion == false && overrideCheckServerVersion != true) {
|
||||
showDialog(
|
||||
context: globalNavigatorKey.currentContext!,
|
||||
builder: (ctx) => UnsupportedVersionModal(
|
||||
serverVersion: status.serverVersion,
|
||||
onClose: () {
|
||||
_serversProvider!.setSelectedServer(null);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
|
@ -292,12 +285,12 @@ class StatusProvider with ChangeNotifier {
|
|||
}) async {
|
||||
if (_serverStatus == null) return false;
|
||||
|
||||
final rules = await _serversProvider!.apiClient!.getFilteringRules();
|
||||
final rules = await _serversProvider!.apiClient2!.getFilteringRules();
|
||||
|
||||
if (rules['result'] == 'success') {
|
||||
if (rules.successful == true) {
|
||||
FilteringStatus oldStatus = _serverStatus!.filteringStatus;
|
||||
|
||||
List<String> newRules = rules['data'].userRules.where((d) => !d.contains(domain)).toList();
|
||||
List<String> newRules = (rules.content as FilteringStatus).userRules.where((d) => !d.contains(domain)).toList();
|
||||
if (newStatus == 'block') {
|
||||
newRules.add("||$domain^");
|
||||
}
|
||||
|
@ -308,9 +301,9 @@ class StatusProvider with ChangeNotifier {
|
|||
newObj.userRules = newRules;
|
||||
_filteringStatus = newObj;
|
||||
|
||||
final result = await _serversProvider!.apiClient!.postFilteringRules(data: {'rules': newRules});
|
||||
final result = await _serversProvider!.apiClient2!.postFilteringRules(data: {'rules': newRules});
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
|
@ -324,11 +317,11 @@ class StatusProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> updateSafeSearchConfig(Map<String, bool> status) async {
|
||||
final result = await _serversProvider!.apiClient!.updateSafeSearchSettings(
|
||||
final result = await _serversProvider!.apiClient2!.updateSafeSearchSettings(
|
||||
body: status
|
||||
);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
if (result.successful == true) {
|
||||
ServerStatus data = serverStatus!;
|
||||
data.safeSearchEnabled = status['enabled'] ?? false;
|
||||
data.safeSeachBing = status['bing'] ?? false;
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
@ -9,11 +7,10 @@ import 'package:flutter_split_view/flutter_split_view.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/client_screen.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/added_client_tile.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/remove_client_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/added_client_tile.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/fab.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/options_modal.dart';
|
||||
import 'package:adguard_home_manager/widgets/tab_content_list.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||
|
@ -32,13 +29,13 @@ class AddedList extends StatefulWidget {
|
|||
final bool splitView;
|
||||
|
||||
const AddedList({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.scrollController,
|
||||
required this.data,
|
||||
required this.onClientSelected,
|
||||
this.selectedClient,
|
||||
required this.splitView
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<AddedList> createState() => _AddedListState();
|
||||
|
@ -77,8 +74,8 @@ class _AddedListState extends State<AddedList> {
|
|||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void confirmEditClient(Client client) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
processModal.open(AppLocalizations.of(context)!.addingClient);
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.savingChanges);
|
||||
|
||||
final result = await clientsProvider.editClient(client);
|
||||
|
||||
|
@ -101,7 +98,7 @@ class _AddedListState extends State<AddedList> {
|
|||
}
|
||||
|
||||
void deleteClient(Client client) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.removingClient);
|
||||
|
||||
final result = await clientsProvider.deleteClient(client);
|
||||
|
@ -128,31 +125,13 @@ class _AddedListState extends State<AddedList> {
|
|||
}
|
||||
|
||||
void openClientModal(Client client) {
|
||||
if (width > 900 || !(Platform.isAndroid | Platform.isIOS)) {
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (BuildContext context) => ClientScreen(
|
||||
onConfirm: confirmEditClient,
|
||||
serverVersion: statusProvider.serverStatus!.serverVersion,
|
||||
onDelete: deleteClient,
|
||||
client: client,
|
||||
dialog: true,
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (BuildContext context) => ClientScreen(
|
||||
onConfirm: confirmEditClient,
|
||||
serverVersion: statusProvider.serverStatus!.serverVersion,
|
||||
onDelete: deleteClient,
|
||||
client: client,
|
||||
dialog: false,
|
||||
)
|
||||
));
|
||||
}
|
||||
openClientFormModal(
|
||||
context: context,
|
||||
width: width,
|
||||
client: client,
|
||||
onConfirm: confirmEditClient,
|
||||
onDelete: deleteClient
|
||||
);
|
||||
}
|
||||
|
||||
void openDeleteModal(Client client) {
|
||||
|
@ -163,19 +142,13 @@ class _AddedListState extends State<AddedList> {
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
void openOptionsModal(Client client) {
|
||||
showModal(
|
||||
context: context,
|
||||
builder: (ctx) => OptionsModal(
|
||||
onDelete: () => openDeleteModal(client),
|
||||
onEdit: () => openClientModal(client),
|
||||
)
|
||||
);
|
||||
}
|
||||
final clientsDisplay = clientsProvider.searchTermClients != null && clientsProvider.searchTermClients != ""
|
||||
? widget.data.where(
|
||||
(c) => c.name.toLowerCase().contains(clientsProvider.searchTermClients.toString()) || c.ids.where((id) => id.contains(clientsProvider.searchTermClients.toString())).isNotEmpty
|
||||
).toList()
|
||||
: widget.data;
|
||||
|
||||
return CustomTabContentList(
|
||||
noSliver: !(Platform.isAndroid || Platform.isIOS),
|
||||
listPadding: widget.splitView == true
|
||||
? const EdgeInsets.only(top: 8)
|
||||
: null,
|
||||
|
@ -198,15 +171,16 @@ class _AddedListState extends State<AddedList> {
|
|||
],
|
||||
),
|
||||
),
|
||||
itemsCount: widget.data.length,
|
||||
itemsCount: clientsDisplay.length,
|
||||
contentWidget: (index) => AddedClientTile(
|
||||
selectedClient: widget.selectedClient,
|
||||
client: widget.data[index],
|
||||
client: clientsDisplay[index],
|
||||
onTap: widget.onClientSelected,
|
||||
onLongPress: openOptionsModal,
|
||||
onEdit: openClientModal,
|
||||
onEdit: statusProvider.serverStatus != null
|
||||
? (c) => openClientModal(c)
|
||||
: null,
|
||||
onDelete: openDeleteModal,
|
||||
splitView: widget.splitView,
|
||||
serverVersion: statusProvider.serverStatus!.serverVersion,
|
||||
),
|
||||
noData: SizedBox(
|
||||
width: double.maxFinite,
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:contextmenu/contextmenu.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/options_menu.dart';
|
||||
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||
import 'package:adguard_home_manager/widgets/options_modal.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/menu_option.dart';
|
||||
import 'package:adguard_home_manager/functions/copy_clipboard.dart';
|
||||
|
@ -16,54 +15,29 @@ class ActiveClientTile extends StatelessWidget {
|
|||
final AutoClient? selectedClient;
|
||||
|
||||
const ActiveClientTile({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.client,
|
||||
required this.onTap,
|
||||
required this.splitView,
|
||||
this.selectedClient
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
void openOptionsModal() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => OptionsModal(
|
||||
options: [
|
||||
MenuOption(
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
icon: Icons.copy_rounded,
|
||||
action: () {
|
||||
copyToClipboard(
|
||||
value: client.name != ''
|
||||
? client.name!
|
||||
: client.ip,
|
||||
successMessage: AppLocalizations.of(context)!.copiedClipboard,
|
||||
);
|
||||
},
|
||||
)
|
||||
]
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (splitView == true) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: ContextMenuArea(
|
||||
builder: (context) => [
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
child: OptionsMenu(
|
||||
options: (_) => [
|
||||
MenuOption(
|
||||
icon: Icons.copy_rounded,
|
||||
onTap: () {
|
||||
copyToClipboard(
|
||||
value: client.name != ''
|
||||
? client.name!
|
||||
: client.ip,
|
||||
successMessage: AppLocalizations.of(context)!.copiedClipboard,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
action: () => copyToClipboard(
|
||||
value: client.name != ''
|
||||
? client.name!
|
||||
: client.ip,
|
||||
successMessage: AppLocalizations.of(context)!.copiedClipboard,
|
||||
)
|
||||
)
|
||||
],
|
||||
child: Material(
|
||||
|
@ -72,10 +46,6 @@ class ActiveClientTile extends StatelessWidget {
|
|||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
onTap: () => onTap(client),
|
||||
onLongPress: () {
|
||||
Navigator.pop(context);
|
||||
openOptionsModal();
|
||||
},
|
||||
child: Container(
|
||||
width: double.maxFinite,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
|
@ -128,20 +98,17 @@ class ActiveClientTile extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
else {
|
||||
return ContextMenuArea(
|
||||
builder: (context) => [
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
return OptionsMenu(
|
||||
options: (_) => [
|
||||
MenuOption(
|
||||
icon: Icons.copy_rounded,
|
||||
onTap: () {
|
||||
copyToClipboard(
|
||||
value: client.name != ''
|
||||
? client.name!
|
||||
: client.ip,
|
||||
successMessage: AppLocalizations.of(context)!.copiedClipboard,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
action: () => copyToClipboard(
|
||||
value: client.name != ''
|
||||
? client.name!
|
||||
: client.ip,
|
||||
successMessage: AppLocalizations.of(context)!.copiedClipboard,
|
||||
)
|
||||
)
|
||||
],
|
||||
child: CustomListTile(
|
||||
|
@ -158,7 +125,6 @@ class ActiveClientTile extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
onTap: () => onTap(client),
|
||||
onLongPress: openOptionsModal,
|
||||
),
|
||||
);
|
||||
}
|
|
@ -1,81 +1,75 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:contextmenu/contextmenu.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/options_menu.dart';
|
||||
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/compare_versions.dart';
|
||||
import 'package:adguard_home_manager/models/menu_option.dart';
|
||||
import 'package:adguard_home_manager/functions/copy_clipboard.dart';
|
||||
import 'package:adguard_home_manager/models/clients.dart';
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
|
||||
class AddedClientTile extends StatelessWidget {
|
||||
class AddedClientTile extends StatefulWidget {
|
||||
final Client client;
|
||||
final void Function(Client) onTap;
|
||||
final void Function(Client) onLongPress;
|
||||
final void Function(Client) onEdit;
|
||||
final void Function(Client)? onEdit;
|
||||
final void Function(Client) onDelete;
|
||||
final Client? selectedClient;
|
||||
final bool? splitView;
|
||||
final String serverVersion;
|
||||
|
||||
const AddedClientTile({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.client,
|
||||
required this.onTap,
|
||||
required this.onLongPress,
|
||||
required this.onEdit,
|
||||
this.onEdit,
|
||||
required this.onDelete,
|
||||
this.selectedClient,
|
||||
required this.splitView,
|
||||
required this.serverVersion
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<AddedClientTile> createState() => _AddedClientTileState();
|
||||
}
|
||||
|
||||
class _AddedClientTileState extends State<AddedClientTile> {
|
||||
bool _isHover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
if (splitView == true) {
|
||||
if (widget.splitView == true) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: ContextMenuArea(
|
||||
builder: (context) => [
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.seeDetails,
|
||||
icon: Icons.file_open_rounded,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
onEdit(client);
|
||||
}
|
||||
),
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
child: OptionsMenu(
|
||||
options: (_) => [
|
||||
MenuOption(
|
||||
icon: Icons.copy_rounded,
|
||||
onTap: () {
|
||||
copyToClipboard(
|
||||
value: client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
|
||||
successMessage: AppLocalizations.of(context)!.copiedClipboard,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
action: () => copyToClipboard(
|
||||
value: widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
|
||||
successMessage: AppLocalizations.of(context)!.copiedClipboard,
|
||||
)
|
||||
),
|
||||
],
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
onTap: () => onTap(client),
|
||||
onLongPress: () => onLongPress(client),
|
||||
onTap: () => widget.onTap(widget.client),
|
||||
onHover: (v) => setState(() => _isHover = v),
|
||||
child: Container(
|
||||
width: double.maxFinite,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
color: client == selectedClient
|
||||
color: widget.client == widget.selectedClient
|
||||
? Theme.of(context).colorScheme.primaryContainer
|
||||
: null
|
||||
),
|
||||
child: Row(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
|
@ -87,7 +81,7 @@ class AddedClientTile extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
|
||||
widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
|
@ -100,7 +94,7 @@ class AddedClientTile extends StatelessWidget {
|
|||
Icon(
|
||||
Icons.filter_list_rounded,
|
||||
size: 19,
|
||||
color: client.filteringEnabled == true
|
||||
color: widget.client.filteringEnabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
|
@ -112,7 +106,7 @@ class AddedClientTile extends StatelessWidget {
|
|||
Icon(
|
||||
Icons.vpn_lock_rounded,
|
||||
size: 18,
|
||||
color: client.safebrowsingEnabled == true
|
||||
color: widget.client.safebrowsingEnabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
|
@ -124,7 +118,7 @@ class AddedClientTile extends StatelessWidget {
|
|||
Icon(
|
||||
Icons.block,
|
||||
size: 18,
|
||||
color: client.parentalEnabled == true
|
||||
color: widget.client.parentalEnabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
|
@ -136,25 +130,13 @@ class AddedClientTile extends StatelessWidget {
|
|||
Icon(
|
||||
Icons.search_rounded,
|
||||
size: 19,
|
||||
color: serverVersionIsAhead(
|
||||
currentVersion: serverVersion,
|
||||
referenceVersion: 'v0.107.28',
|
||||
referenceVersionBeta: 'v0.108.0-b.33'
|
||||
) == true
|
||||
? client.safeSearch != null && client.safeSearch!.enabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red
|
||||
: client.safesearchEnabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red,
|
||||
color: widget.client.safeSearch != null && widget.client.safeSearch!.enabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red
|
||||
)
|
||||
],
|
||||
)
|
||||
|
@ -164,6 +146,14 @@ class AddedClientTile extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
if (widget.onEdit != null && _isHover == true) ...[
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
onPressed: () => widget.onEdit!(widget.client),
|
||||
icon: const Icon(Icons.file_open_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.seeDetails,
|
||||
)
|
||||
]
|
||||
],
|
||||
)
|
||||
),
|
||||
|
@ -173,37 +163,30 @@ class AddedClientTile extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
else {
|
||||
return ContextMenuArea(
|
||||
builder: (context) => [
|
||||
CustomListTile(
|
||||
return OptionsMenu(
|
||||
options: (_) => [
|
||||
if (widget.onEdit != null) MenuOption(
|
||||
title: AppLocalizations.of(context)!.seeDetails,
|
||||
icon: Icons.file_open_rounded,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
onEdit(client);
|
||||
}
|
||||
action: () => widget.onEdit!(widget.client)
|
||||
),
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
MenuOption(
|
||||
icon: Icons.copy_rounded,
|
||||
onTap: () {
|
||||
copyToClipboard(
|
||||
value: client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
|
||||
successMessage: AppLocalizations.of(context)!.copiedClipboard,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
action: () => copyToClipboard(
|
||||
value: widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
|
||||
successMessage: AppLocalizations.of(context)!.copiedClipboard,
|
||||
)
|
||||
),
|
||||
],
|
||||
child: CustomListTile(
|
||||
onLongPress: () => onLongPress(client),
|
||||
onTap: () => onTap(client),
|
||||
title: client.name,
|
||||
onTap: () => widget.onTap(widget.client),
|
||||
title: widget.client.name,
|
||||
subtitleWidget: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
|
||||
widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).listTileTheme.textColor
|
||||
),
|
||||
|
@ -214,7 +197,7 @@ class AddedClientTile extends StatelessWidget {
|
|||
Icon(
|
||||
Icons.filter_list_rounded,
|
||||
size: 19,
|
||||
color: client.filteringEnabled == true
|
||||
color: widget.client.filteringEnabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
|
@ -226,7 +209,7 @@ class AddedClientTile extends StatelessWidget {
|
|||
Icon(
|
||||
Icons.vpn_lock_rounded,
|
||||
size: 18,
|
||||
color: client.safebrowsingEnabled == true
|
||||
color: widget.client.safebrowsingEnabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
|
@ -238,7 +221,7 @@ class AddedClientTile extends StatelessWidget {
|
|||
Icon(
|
||||
Icons.block,
|
||||
size: 18,
|
||||
color: client.parentalEnabled == true
|
||||
color: widget.client.parentalEnabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
|
@ -250,25 +233,13 @@ class AddedClientTile extends StatelessWidget {
|
|||
Icon(
|
||||
Icons.search_rounded,
|
||||
size: 19,
|
||||
color: serverVersionIsAhead(
|
||||
currentVersion: serverVersion,
|
||||
referenceVersion: 'v0.107.28',
|
||||
referenceVersionBeta: 'v0.108.0-b.33'
|
||||
) == true
|
||||
? client.safeSearch != null && client.safeSearch!.enabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red
|
||||
: client.safesearchEnabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red,
|
||||
color: widget.client.safeSearch != null && widget.client.safeSearch!.enabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red
|
||||
)
|
||||
],
|
||||
)
|
118
lib/screens/clients/client/blocked_services_section.dart
Normal file
118
lib/screens/clients/client/blocked_services_section.dart
Normal file
|
@ -0,0 +1,118 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
|
||||
|
||||
class BlockedServicesSection extends StatelessWidget {
|
||||
final bool useGlobalSettingsServices;
|
||||
final List<String> blockedServices;
|
||||
final void Function(List<String>) onUpdatedBlockedServices;
|
||||
final void Function(bool) onUpdateServicesGlobalSettings;
|
||||
|
||||
const BlockedServicesSection({
|
||||
super.key,
|
||||
required this.useGlobalSettingsServices,
|
||||
required this.blockedServices,
|
||||
required this.onUpdatedBlockedServices,
|
||||
required this.onUpdateServicesGlobalSettings
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: InkWell(
|
||||
onTap: () => onUpdateServicesGlobalSettings(!useGlobalSettingsServices),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 6
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.useGlobalSettings,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: useGlobalSettingsServices,
|
||||
onChanged: (value) => onUpdateServicesGlobalSettings(value),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: useGlobalSettingsServices == false
|
||||
? () => openServicesModal(
|
||||
context: context,
|
||||
blockedServices: blockedServices,
|
||||
onUpdateBlockedServices: onUpdatedBlockedServices
|
||||
)
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 32
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.public,
|
||||
color: useGlobalSettingsServices == false
|
||||
? Theme.of(context).listTileTheme.iconColor
|
||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.38),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.selectBlockedServices,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: useGlobalSettingsServices == false
|
||||
? Theme.of(context).colorScheme.onSurface
|
||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.38),
|
||||
),
|
||||
),
|
||||
if (useGlobalSettingsServices == false) ...[
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
blockedServices.isNotEmpty
|
||||
? "${blockedServices.length} ${AppLocalizations.of(context)!.servicesBlocked}"
|
||||
: AppLocalizations.of(context)!.noBlockedServicesSelected,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
288
lib/screens/clients/client/client_form.dart
Normal file
288
lib/screens/clients/client/client_form.dart
Normal file
|
@ -0,0 +1,288 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/client/blocked_services_section.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/client_screen.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/identifiers_section.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/settings_tile.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/tags_section.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/upstream_servers_section.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart';
|
||||
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||
import 'package:adguard_home_manager/widgets/section_label.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/clients.dart';
|
||||
import 'package:adguard_home_manager/models/safe_search.dart';
|
||||
|
||||
class ClientForm extends StatelessWidget {
|
||||
final bool isFullScreen;
|
||||
final Client? client;
|
||||
final TextEditingController nameController;
|
||||
final List<ControllerListItem> identifiersControllers;
|
||||
final List<String> selectedTags;
|
||||
final bool useGlobalSettingsFiltering;
|
||||
final bool? enableFiltering;
|
||||
final bool? enableSafeBrowsing;
|
||||
final bool? enableParentalControl;
|
||||
final bool? enableSafeSearch;
|
||||
final SafeSearch? safeSearch;
|
||||
final SafeSearch defaultSafeSearch;
|
||||
final bool useGlobalSettingsServices;
|
||||
final List<String> blockedServices;
|
||||
final void Function(List<String>) updateBlockedServices;
|
||||
final List<ControllerListItem> upstreamServers;
|
||||
final void Function(List<ControllerListItem>) updateUpstreamServers;
|
||||
final void Function(List<String>) updateSelectedTags;
|
||||
final void Function(List<ControllerListItem>) updateIdentifiersControllers;
|
||||
final void Function() enableDisableGlobalSettingsFiltering;
|
||||
final void Function(bool) updateEnableFiltering;
|
||||
final void Function(bool) updateEnableSafeBrowsing;
|
||||
final void Function(bool) updateEnableParentalControl;
|
||||
final void Function(bool) updateEnableSafeSearch;
|
||||
final void Function(SafeSearch) updateSafeSearch;
|
||||
final void Function(bool) updateUseGlobalSettingsServices;
|
||||
final bool ignoreClientQueryLog;
|
||||
final void Function(bool) updateIgnoreClientQueryLog;
|
||||
final bool ignoreClientStatistics;
|
||||
final void Function(bool) updateIgnoreClientStatistics;
|
||||
final bool enableDnsCache;
|
||||
final void Function(bool) updateEnableDnsCache;
|
||||
final TextEditingController dnsCacheField;
|
||||
final String? dnsCacheError;
|
||||
final void Function(String?) updateDnsCacheError;
|
||||
|
||||
const ClientForm({
|
||||
super.key,
|
||||
required this.isFullScreen,
|
||||
required this.client,
|
||||
required this.nameController,
|
||||
required this.identifiersControllers,
|
||||
required this.selectedTags,
|
||||
required this.useGlobalSettingsFiltering,
|
||||
required this.enableFiltering,
|
||||
required this.enableParentalControl,
|
||||
required this.enableSafeBrowsing,
|
||||
required this.enableSafeSearch,
|
||||
required this.safeSearch,
|
||||
required this.blockedServices,
|
||||
required this.updateBlockedServices,
|
||||
required this.upstreamServers,
|
||||
required this.updateUpstreamServers,
|
||||
required this.defaultSafeSearch,
|
||||
required this.useGlobalSettingsServices,
|
||||
required this.updateSelectedTags,
|
||||
required this.updateIdentifiersControllers,
|
||||
required this.enableDisableGlobalSettingsFiltering,
|
||||
required this.updateEnableFiltering,
|
||||
required this.updateEnableParentalControl,
|
||||
required this.updateEnableSafeBrowsing,
|
||||
required this.updateEnableSafeSearch,
|
||||
required this.updateSafeSearch,
|
||||
required this.updateUseGlobalSettingsServices,
|
||||
required this.ignoreClientQueryLog,
|
||||
required this.ignoreClientStatistics,
|
||||
required this.updateIgnoreClientQueryLog,
|
||||
required this.updateIgnoreClientStatistics,
|
||||
required this.enableDnsCache,
|
||||
required this.updateEnableDnsCache,
|
||||
required this.dnsCacheField,
|
||||
required this.dnsCacheError,
|
||||
required this.updateDnsCacheError,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: TextFormField(
|
||||
enabled: client != null ? false : true,
|
||||
controller: nameController,
|
||||
onChanged: (_) => {},
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.badge_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
labelText: AppLocalizations.of(context)!.name,
|
||||
),
|
||||
),
|
||||
),
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.tags,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
|
||||
),
|
||||
TagsSection(
|
||||
selectedTags: selectedTags,
|
||||
onTagsSelected: updateSelectedTags
|
||||
),
|
||||
IdentifiersSection(
|
||||
identifiersControllers: identifiersControllers,
|
||||
onUpdateIdentifiersControllers: (c) {
|
||||
updateIdentifiersControllers(c);
|
||||
},
|
||||
onCheckValidValues: () => {}
|
||||
),
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.settings,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16, right: 16, top: 12, bottom: 24
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: InkWell(
|
||||
onTap: enableDisableGlobalSettingsFiltering,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 6
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.useGlobalSettings,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: useGlobalSettingsFiltering,
|
||||
onChanged: (value) => enableDisableGlobalSettingsFiltering()
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SettingsTile(
|
||||
label: AppLocalizations.of(context)!.enableFiltering,
|
||||
value: enableFiltering,
|
||||
onChange: (value) => updateEnableFiltering(value),
|
||||
useGlobalSettingsFiltering: useGlobalSettingsFiltering,
|
||||
),
|
||||
SettingsTile(
|
||||
label: AppLocalizations.of(context)!.enableSafeBrowsing,
|
||||
value: enableSafeBrowsing,
|
||||
onChange: (value) => updateEnableSafeBrowsing(value),
|
||||
useGlobalSettingsFiltering: useGlobalSettingsFiltering,
|
||||
),
|
||||
SettingsTile(
|
||||
label: AppLocalizations.of(context)!.enableParentalControl,
|
||||
value: enableParentalControl,
|
||||
onChange: (value) => updateEnableParentalControl(value),
|
||||
useGlobalSettingsFiltering: useGlobalSettingsFiltering,
|
||||
),
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.safeSearch,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 34,
|
||||
vertical: 16
|
||||
),
|
||||
trailing: Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: useGlobalSettingsFiltering == true
|
||||
? Colors.grey
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
onTap: useGlobalSettingsFiltering == false
|
||||
? () => openSafeSearchModal(
|
||||
context: context,
|
||||
blockedServices: blockedServices,
|
||||
defaultSafeSearch: defaultSafeSearch,
|
||||
safeSearch: safeSearch,
|
||||
onUpdateSafeSearch: updateSafeSearch
|
||||
)
|
||||
: null,
|
||||
),
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.queryLogsAndStatistics,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
|
||||
),
|
||||
CustomSwitchListTile(
|
||||
title: AppLocalizations.of(context)!.ignoreClientQueryLog,
|
||||
value: ignoreClientQueryLog,
|
||||
onChanged: updateIgnoreClientQueryLog,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 6
|
||||
),
|
||||
),
|
||||
CustomSwitchListTile(
|
||||
title: AppLocalizations.of(context)!.ignoreClientStatistics,
|
||||
value: ignoreClientStatistics,
|
||||
onChanged: updateIgnoreClientStatistics,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 6
|
||||
),
|
||||
),
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.blockedServices,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
|
||||
),
|
||||
BlockedServicesSection(
|
||||
useGlobalSettingsServices: useGlobalSettingsServices,
|
||||
blockedServices: blockedServices,
|
||||
onUpdatedBlockedServices: updateBlockedServices,
|
||||
onUpdateServicesGlobalSettings: updateUseGlobalSettingsServices,
|
||||
),
|
||||
UpstreamServersSection(
|
||||
upstreamServers: upstreamServers,
|
||||
onCheckValidValues: () => {},
|
||||
onUpdateUpstreamServers: updateUpstreamServers
|
||||
),
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.upstreamDnsCacheConfiguration,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
|
||||
),
|
||||
CustomSwitchListTile(
|
||||
title: AppLocalizations.of(context)!.enableDnsCachingClient,
|
||||
value: enableDnsCache,
|
||||
onChanged: updateEnableDnsCache,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 6
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
|
||||
child: TextFormField(
|
||||
controller: dnsCacheField,
|
||||
onChanged: (v) => updateDnsCacheError(!validateNumber(v) ? AppLocalizations.of(context)!.invalidValue : null),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.storage_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
labelText: AppLocalizations.of(context)!.dnsCacheSize,
|
||||
errorText: dnsCacheError
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
12
lib/screens/clients/client/client_placeholder.dart
Normal file
12
lib/screens/clients/client/client_placeholder.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class ClientPlaceholder extends StatelessWidget {
|
||||
const ClientPlaceholder({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Text("Select a client"),
|
||||
);
|
||||
}
|
||||
}
|
421
lib/screens/clients/client/client_screen.dart
Normal file
421
lib/screens/clients/client/client_screen.dart
Normal file
|
@ -0,0 +1,421 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/client/client_form.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/safe_search.dart';
|
||||
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||
import 'package:adguard_home_manager/models/clients.dart';
|
||||
|
||||
class ControllerListItem {
|
||||
final String id;
|
||||
final TextEditingController controller;
|
||||
|
||||
const ControllerListItem({
|
||||
required this.id,
|
||||
required this.controller
|
||||
});
|
||||
}
|
||||
|
||||
class ClientScreen extends StatefulWidget {
|
||||
final Client? client;
|
||||
final void Function(Client) onConfirm;
|
||||
final void Function(Client)? onDelete;
|
||||
final bool fullScreen;
|
||||
|
||||
const ClientScreen({
|
||||
super.key,
|
||||
this.client,
|
||||
required this.onConfirm,
|
||||
this.onDelete,
|
||||
required this.fullScreen
|
||||
});
|
||||
|
||||
@override
|
||||
State<ClientScreen> createState() => _ClientScreenState();
|
||||
}
|
||||
|
||||
class _ClientScreenState extends State<ClientScreen> {
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
final Uuid uuid = const Uuid();
|
||||
|
||||
bool validValues = false;
|
||||
|
||||
TextEditingController nameController = TextEditingController();
|
||||
|
||||
List<String> selectedTags = [];
|
||||
|
||||
List<ControllerListItem> identifiersControllers = [
|
||||
ControllerListItem(id: "0", controller: TextEditingController())
|
||||
];
|
||||
|
||||
bool useGlobalSettingsFiltering = true;
|
||||
bool? enableFiltering;
|
||||
bool? enableSafeBrowsing;
|
||||
bool? enableParentalControl;
|
||||
bool? enableSafeSearch;
|
||||
SafeSearch? safeSearch;
|
||||
|
||||
final SafeSearch defaultSafeSearch = SafeSearch(
|
||||
enabled: false,
|
||||
bing: false,
|
||||
duckduckgo: false,
|
||||
google: false,
|
||||
pixabay: false,
|
||||
yandex: false,
|
||||
youtube: false
|
||||
);
|
||||
|
||||
bool useGlobalSettingsServices = true;
|
||||
List<String> blockedServices = [];
|
||||
|
||||
List<ControllerListItem> upstreamServers = [];
|
||||
|
||||
bool _ignoreClientQueryLog = false;
|
||||
bool _ignoreClientStatistics = false;
|
||||
|
||||
bool _enableDnsCache = false;
|
||||
final _dnsCacheField = TextEditingController();
|
||||
String? _dnsCacheError;
|
||||
|
||||
// VALIDATIONS
|
||||
bool _nameValid = true;
|
||||
bool _identifiersValid = true;
|
||||
bool _dnsCacheValid = true;
|
||||
|
||||
void enableDisableGlobalSettingsFiltering() {
|
||||
if (useGlobalSettingsFiltering == true) {
|
||||
setState(() {
|
||||
useGlobalSettingsFiltering = false;
|
||||
|
||||
enableFiltering = false;
|
||||
enableSafeBrowsing = false;
|
||||
enableParentalControl = false;
|
||||
enableSafeSearch = false;
|
||||
safeSearch = defaultSafeSearch;
|
||||
});
|
||||
}
|
||||
else if (useGlobalSettingsFiltering == false) {
|
||||
setState(() {
|
||||
useGlobalSettingsFiltering = true;
|
||||
|
||||
enableFiltering = null;
|
||||
enableSafeBrowsing = null;
|
||||
enableParentalControl = null;
|
||||
enableSafeSearch = null;
|
||||
safeSearch = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
if (widget.client != null) {
|
||||
validValues = true;
|
||||
|
||||
nameController.text = widget.client!.name;
|
||||
selectedTags = widget.client!.tags;
|
||||
identifiersControllers = widget.client!.ids.map((e) => ControllerListItem(
|
||||
id: uuid.v4(),
|
||||
controller: TextEditingController(text: e)
|
||||
)).toList();
|
||||
useGlobalSettingsFiltering = widget.client!.useGlobalSettings;
|
||||
enableFiltering = widget.client!.filteringEnabled;
|
||||
enableParentalControl = widget.client!.parentalEnabled;
|
||||
enableSafeBrowsing = widget.client!.safebrowsingEnabled;
|
||||
safeSearch = widget.client!.safeSearch;
|
||||
useGlobalSettingsServices = widget.client!.useGlobalBlockedServices;
|
||||
blockedServices = widget.client!.blockedServices;
|
||||
upstreamServers = widget.client!.upstreams.map((e) => ControllerListItem(
|
||||
id: uuid.v4(),
|
||||
controller: TextEditingController(text: e)
|
||||
)).toList();
|
||||
_ignoreClientQueryLog = widget.client!.ignoreQuerylog ?? false;
|
||||
_ignoreClientStatistics = widget.client!.ignoreStatistics ?? false;
|
||||
_enableDnsCache = widget.client!.upstreamsCacheEnabled ?? false;
|
||||
_dnsCacheField.text = widget.client!.upstreamsCacheSize != null
|
||||
? widget.client!.upstreamsCacheSize.toString()
|
||||
: "";
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final clientsProvider = Provider.of<ClientsProvider>(context);
|
||||
|
||||
void createClient() {
|
||||
final Client client = Client(
|
||||
name: nameController.text,
|
||||
ids: List<String>.from(identifiersControllers.map((e) => e.controller.text)),
|
||||
useGlobalSettings: useGlobalSettingsFiltering,
|
||||
filteringEnabled: enableFiltering ?? false,
|
||||
parentalEnabled: enableParentalControl ?? false,
|
||||
safebrowsingEnabled: enableSafeBrowsing ?? false,
|
||||
safeSearch: safeSearch,
|
||||
useGlobalBlockedServices: useGlobalSettingsServices,
|
||||
blockedServices: blockedServices,
|
||||
upstreams: List<String>.from(upstreamServers.map((e) => e.controller.text)),
|
||||
tags: selectedTags,
|
||||
ignoreQuerylog: _ignoreClientQueryLog,
|
||||
ignoreStatistics: _ignoreClientStatistics,
|
||||
upstreamsCacheEnabled: _enableDnsCache,
|
||||
upstreamsCacheSize: _dnsCacheField.text != ""
|
||||
? int.parse(_dnsCacheField.text)
|
||||
: null
|
||||
);
|
||||
widget.onConfirm(client);
|
||||
}
|
||||
|
||||
void validateValues() {
|
||||
_nameValid = nameController.text != '';
|
||||
_identifiersValid = identifiersControllers.isNotEmpty && identifiersControllers[0].controller.text != '';
|
||||
_dnsCacheValid = (_dnsCacheField.text == "" || _dnsCacheField.text != "" && RegExp(r'^\d+$').hasMatch(_dnsCacheField.text));
|
||||
if (_nameValid && _identifiersValid && _dnsCacheValid) {
|
||||
createClient();
|
||||
Navigator.pop(context);
|
||||
}
|
||||
else {
|
||||
_scrollController.animateTo(
|
||||
0,
|
||||
curve: Curves.easeOut,
|
||||
duration: const Duration(milliseconds: 500)
|
||||
);
|
||||
setState(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> actions() {
|
||||
return [
|
||||
IconButton(
|
||||
onPressed: validateValues,
|
||||
icon: const Icon(Icons.save_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.save,
|
||||
),
|
||||
if (widget.client != null) IconButton(
|
||||
onPressed: () => openDeleteClientScreen(
|
||||
context: context,
|
||||
onDelete: () => clientsProvider.deleteClient(widget.client!),
|
||||
),
|
||||
icon: const Icon(Icons.delete_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.delete,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
if (widget.fullScreen == true) {
|
||||
return Dialog.fullscreen(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.close)
|
||||
),
|
||||
title: Text(
|
||||
widget.client != null
|
||||
? AppLocalizations.of(context)!.client
|
||||
: AppLocalizations.of(context)!.addClient
|
||||
),
|
||||
actions: actions(),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
controller: _scrollController,
|
||||
children: [
|
||||
if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors(
|
||||
nameValid: _nameValid,
|
||||
identifiersValid: _identifiersValid,
|
||||
dnsCacheValid: _dnsCacheValid
|
||||
),
|
||||
ClientForm(
|
||||
isFullScreen: true,
|
||||
client: widget.client,
|
||||
nameController: nameController,
|
||||
identifiersControllers: identifiersControllers,
|
||||
selectedTags: selectedTags,
|
||||
useGlobalSettingsFiltering: useGlobalSettingsFiltering,
|
||||
enableFiltering: enableFiltering,
|
||||
enableParentalControl: enableParentalControl,
|
||||
enableSafeBrowsing: enableSafeBrowsing,
|
||||
enableSafeSearch: enableSafeSearch,
|
||||
safeSearch: safeSearch,
|
||||
blockedServices: blockedServices,
|
||||
updateBlockedServices: (v) => setState(() => blockedServices = v),
|
||||
upstreamServers: upstreamServers,
|
||||
updateUpstreamServers: (v) => setState(() => upstreamServers = v),
|
||||
defaultSafeSearch: defaultSafeSearch,
|
||||
useGlobalSettingsServices: useGlobalSettingsServices,
|
||||
updateSelectedTags: (v) => setState(() => selectedTags = v),
|
||||
updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v),
|
||||
enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering,
|
||||
updateEnableFiltering: (v) => setState(() => enableFiltering = v),
|
||||
updateEnableParentalControl: (v) => setState(() => enableParentalControl = v),
|
||||
updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v),
|
||||
updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v),
|
||||
updateSafeSearch: (v) => setState(() => safeSearch = v),
|
||||
updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v),
|
||||
ignoreClientQueryLog: _ignoreClientQueryLog,
|
||||
ignoreClientStatistics: _ignoreClientStatistics,
|
||||
updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v),
|
||||
updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v),
|
||||
enableDnsCache: _enableDnsCache,
|
||||
updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v),
|
||||
dnsCacheField: _dnsCacheField,
|
||||
dnsCacheError: _dnsCacheError,
|
||||
updateDnsCacheError: (v) => setState(() => _dnsCacheError = v)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CloseButton(onPressed: () => Navigator.pop(context)),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
widget.client != null
|
||||
? AppLocalizations.of(context)!.client
|
||||
: AppLocalizations.of(context)!.addClient,
|
||||
style: const TextStyle(
|
||||
fontSize: 22
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: actions()
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: ListView(
|
||||
controller: _scrollController,
|
||||
children: [
|
||||
if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors(
|
||||
nameValid: _nameValid,
|
||||
identifiersValid: _identifiersValid,
|
||||
dnsCacheValid: _dnsCacheValid
|
||||
),
|
||||
ClientForm(
|
||||
isFullScreen: false,
|
||||
client: widget.client,
|
||||
nameController: nameController,
|
||||
identifiersControllers: identifiersControllers,
|
||||
selectedTags: selectedTags,
|
||||
useGlobalSettingsFiltering: useGlobalSettingsFiltering,
|
||||
enableFiltering: enableFiltering,
|
||||
enableParentalControl: enableParentalControl,
|
||||
enableSafeBrowsing: enableSafeBrowsing,
|
||||
enableSafeSearch: enableSafeSearch,
|
||||
safeSearch: safeSearch,
|
||||
blockedServices: blockedServices,
|
||||
updateBlockedServices: (v) => setState(() => blockedServices = v),
|
||||
upstreamServers: upstreamServers,
|
||||
updateUpstreamServers: (v) => setState(() => upstreamServers = v),
|
||||
defaultSafeSearch: defaultSafeSearch,
|
||||
useGlobalSettingsServices: useGlobalSettingsServices,
|
||||
updateSelectedTags: (v) => setState(() => selectedTags = v),
|
||||
updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v),
|
||||
enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering,
|
||||
updateEnableFiltering: (v) => setState(() => enableFiltering = v),
|
||||
updateEnableParentalControl: (v) => setState(() => enableParentalControl = v),
|
||||
updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v),
|
||||
updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v),
|
||||
updateSafeSearch: (v) => setState(() => safeSearch = v),
|
||||
updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v),
|
||||
ignoreClientQueryLog: _ignoreClientQueryLog,
|
||||
ignoreClientStatistics: _ignoreClientStatistics,
|
||||
updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v),
|
||||
updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v),
|
||||
enableDnsCache: _enableDnsCache,
|
||||
updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v),
|
||||
dnsCacheField: _dnsCacheField,
|
||||
dnsCacheError: _dnsCacheError,
|
||||
updateDnsCacheError: (v) => setState(() => _dnsCacheError = v)
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Errors extends StatelessWidget {
|
||||
final bool nameValid;
|
||||
final bool identifiersValid;
|
||||
final bool dnsCacheValid;
|
||||
|
||||
const _Errors({
|
||||
required this.nameValid,
|
||||
required this.identifiersValid,
|
||||
required this.dnsCacheValid,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
elevation: 0,
|
||||
color: Colors.red.withOpacity(0.2),
|
||||
margin: const EdgeInsets.all(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.errors,
|
||||
style: const TextStyle(
|
||||
fontSize: 18
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (!nameValid) Text(
|
||||
"● ${AppLocalizations.of(context)!.nameInvalid}",
|
||||
style: const TextStyle(
|
||||
fontSize: 14
|
||||
),
|
||||
),
|
||||
if (!identifiersValid) Text(
|
||||
"● ${AppLocalizations.of(context)!.oneIdentifierRequired}",
|
||||
style: const TextStyle(
|
||||
fontSize: 14
|
||||
),
|
||||
),
|
||||
if (!dnsCacheValid) Text(
|
||||
"● ${AppLocalizations.of(context)!.dnsCacheNumber}",
|
||||
style: const TextStyle(
|
||||
fontSize: 14
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
116
lib/screens/clients/client/client_screen_functions.dart
Normal file
116
lib/screens/clients/client/client_screen_functions.dart
Normal file
|
@ -0,0 +1,116 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/client/client_screen.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/safe_search_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/services_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/tags_modal.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/clients.dart';
|
||||
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||
import 'package:adguard_home_manager/models/safe_search.dart';
|
||||
|
||||
void openTagsModal({
|
||||
required BuildContext context,
|
||||
required List<String> selectedTags,
|
||||
required void Function(List<String>) onSelectedTags
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => TagsModal(
|
||||
selectedTags: selectedTags,
|
||||
tags: Provider.of<ClientsProvider>(context, listen: false).clients!.supportedTags,
|
||||
onConfirm: onSelectedTags,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void openServicesModal({
|
||||
required BuildContext context,
|
||||
required List<String> blockedServices,
|
||||
required void Function(List<String>) onUpdateBlockedServices
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ServicesModal(
|
||||
blockedServices: blockedServices,
|
||||
onConfirm: onUpdateBlockedServices,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void openDeleteClientScreen({
|
||||
required BuildContext context,
|
||||
required void Function() onDelete
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => RemoveClientModal(
|
||||
onConfirm: () {
|
||||
Navigator.pop(context);
|
||||
onDelete();
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void openSafeSearchModal({
|
||||
required BuildContext context,
|
||||
required List<String> blockedServices,
|
||||
required void Function(SafeSearch) onUpdateSafeSearch,
|
||||
required SafeSearch? safeSearch,
|
||||
required SafeSearch defaultSafeSearch
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => SafeSearchModal(
|
||||
safeSearch: safeSearch ?? defaultSafeSearch,
|
||||
disabled: false,
|
||||
onConfirm: onUpdateSafeSearch
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void openClientFormModal({
|
||||
required BuildContext context,
|
||||
required double width,
|
||||
Client? client,
|
||||
required void Function(Client) onConfirm,
|
||||
void Function(Client)? onDelete,
|
||||
}) {
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
barrierColor: !(width > 900 || !(Platform.isAndroid | Platform.isIOS))
|
||||
?Colors.transparent
|
||||
: Colors.black54,
|
||||
transitionBuilder: (context, anim1, anim2, child) {
|
||||
return SlideTransition(
|
||||
position: Tween(
|
||||
begin: const Offset(0, 1),
|
||||
end: const Offset(0, 0)
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: anim1,
|
||||
curve: Curves.easeInOutCubicEmphasized
|
||||
)
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
pageBuilder: (context, animation, secondaryAnimation) => ClientScreen(
|
||||
fullScreen: !(width > 900 || !(Platform.isAndroid | Platform.isIOS)),
|
||||
client: client,
|
||||
onConfirm: onConfirm,
|
||||
onDelete: onDelete,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool validateNumber(String value) {
|
||||
if (value == "") return true;
|
||||
final regexp = RegExp(r'^\d+$');
|
||||
return regexp.hasMatch(value);
|
||||
}
|
105
lib/screens/clients/client/identifiers_section.dart
Normal file
105
lib/screens/clients/client/identifiers_section.dart
Normal file
|
@ -0,0 +1,105 @@
|
|||
import 'package:adguard_home_manager/screens/clients/client/client_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/section_label.dart';
|
||||
|
||||
class IdentifiersSection extends StatefulWidget {
|
||||
final List<ControllerListItem> identifiersControllers;
|
||||
final void Function(List<ControllerListItem>) onUpdateIdentifiersControllers;
|
||||
final void Function() onCheckValidValues;
|
||||
|
||||
const IdentifiersSection({
|
||||
super.key,
|
||||
required this.identifiersControllers,
|
||||
required this.onUpdateIdentifiersControllers,
|
||||
required this.onCheckValidValues
|
||||
});
|
||||
|
||||
@override
|
||||
State<IdentifiersSection> createState() => _IdentifiersSectionState();
|
||||
}
|
||||
|
||||
class _IdentifiersSectionState extends State<IdentifiersSection> {
|
||||
final Uuid uuid = const Uuid();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.identifiers,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16, right: 16, top: 24, bottom: 12
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: IconButton(
|
||||
onPressed: () => widget.onUpdateIdentifiersControllers([
|
||||
...widget.identifiersControllers,
|
||||
ControllerListItem(
|
||||
id: uuid.v4(),
|
||||
controller: TextEditingController()
|
||||
),
|
||||
]),
|
||||
icon: const Icon(Icons.add)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (widget.identifiersControllers.isNotEmpty) ...widget.identifiersControllers.map((controller) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 12, bottom: 12, left: 16, right: 10
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: controller.controller,
|
||||
onChanged: (_) => widget.onCheckValidValues(),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.tag),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
helperText: AppLocalizations.of(context)!.identifierHelper,
|
||||
labelText: AppLocalizations.of(context)!.identifier,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24),
|
||||
child: IconButton(
|
||||
onPressed: () => widget.onUpdateIdentifiersControllers(
|
||||
widget.identifiersControllers.where((e) => e.id != controller.id).toList()
|
||||
),
|
||||
icon: const Icon(Icons.remove_circle_outline_outlined)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
if (widget.identifiersControllers.isEmpty) Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.noIdentifiers,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
276
lib/screens/clients/client/logs_list_client.dart
Normal file
276
lib/screens/clients/client/logs_list_client.dart
Normal file
|
@ -0,0 +1,276 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:async/async.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/logs/log_tile.dart';
|
||||
import 'package:adguard_home_manager/screens/logs/details/log_details_screen.dart';
|
||||
|
||||
import 'package:adguard_home_manager/services/api_client.dart';
|
||||
import 'package:adguard_home_manager/functions/desktop_mode.dart';
|
||||
import 'package:adguard_home_manager/models/logs.dart';
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||
|
||||
class LogsListClient extends StatefulWidget {
|
||||
final String ip;
|
||||
final String? name;
|
||||
final ServersProvider serversProvider;
|
||||
final AppConfigProvider appConfigProvider;
|
||||
final bool splitView;
|
||||
|
||||
const LogsListClient({
|
||||
super.key,
|
||||
required this.ip,
|
||||
this.name,
|
||||
required this.serversProvider,
|
||||
required this.appConfigProvider,
|
||||
required this.splitView,
|
||||
});
|
||||
|
||||
@override
|
||||
State<LogsListClient> createState() => _LogsListClientState();
|
||||
}
|
||||
|
||||
class _LogsListClientState extends State<LogsListClient> {
|
||||
late ScrollController scrollController;
|
||||
|
||||
bool isLoadingMore = false;
|
||||
|
||||
int logsQuantity = 100;
|
||||
int offset = 0;
|
||||
|
||||
int loadStatus = 0;
|
||||
LogsData? logsData;
|
||||
|
||||
String previousIp = "";
|
||||
|
||||
bool showDivider = true;
|
||||
|
||||
CancelableOperation? cancelableRequest;
|
||||
|
||||
Future fetchLogs({
|
||||
int? inOffset,
|
||||
bool? loadingMore,
|
||||
String? responseStatus,
|
||||
String? searchText,
|
||||
}) async {
|
||||
final serversProvider = Provider.of<ServersProvider>(context, listen: false);
|
||||
|
||||
int offst = inOffset ?? offset;
|
||||
|
||||
if (loadingMore != null && loadingMore == true) {
|
||||
setState(() => isLoadingMore = true);
|
||||
}
|
||||
|
||||
if (cancelableRequest != null) cancelableRequest!.cancel();
|
||||
|
||||
cancelableRequest = CancelableOperation.fromFuture(
|
||||
serversProvider.apiClient2!.getLogs(
|
||||
count: logsQuantity,
|
||||
offset: offst,
|
||||
search: '"${widget.ip}"'
|
||||
)
|
||||
);
|
||||
|
||||
final result = await cancelableRequest?.value as ApiResponse;
|
||||
if (!mounted) return;
|
||||
|
||||
if (loadingMore != null && loadingMore == true && mounted) {
|
||||
setState(() => isLoadingMore = false);
|
||||
}
|
||||
|
||||
if (result.successful == true) {
|
||||
setState(() => offset = inOffset != null ? inOffset+logsQuantity : offset+logsQuantity);
|
||||
if (loadingMore != null && loadingMore == true && logsData != null) {
|
||||
LogsData newLogsData = result.content;
|
||||
newLogsData.data = [...logsData!.data, ...result.content.data];
|
||||
setState(() => logsData = newLogsData);
|
||||
}
|
||||
else {
|
||||
LogsData newLogsData = result.content;
|
||||
setState(() => logsData = newLogsData);
|
||||
}
|
||||
setState(() => loadStatus = 1);
|
||||
}
|
||||
else {
|
||||
setState(() => loadStatus = 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void scrollListener() {
|
||||
if (scrollController.position.extentAfter < 500 && isLoadingMore == false) {
|
||||
fetchLogs(loadingMore: true);
|
||||
}
|
||||
if (scrollController.position.pixels > 0) {
|
||||
setState(() => showDivider = false);
|
||||
}
|
||||
else {
|
||||
setState(() => showDivider = true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
scrollController = ScrollController()..addListener(scrollListener);
|
||||
fetchLogs(inOffset: 0);
|
||||
setState(() => previousIp = widget.ip);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
if (widget.ip != previousIp) {
|
||||
setState(() => loadStatus = 0);
|
||||
fetchLogs(inOffset: 0);
|
||||
setState(() => previousIp = widget.ip);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.name != null && widget.name != '' ? widget.name! : widget.ip),
|
||||
centerTitle: true,
|
||||
surfaceTintColor: isDesktop(MediaQuery.of(context).size.width)
|
||||
? Colors.transparent
|
||||
: null,
|
||||
actions: [
|
||||
if (!(Platform.isAndroid || Platform.isIOS)) ...[
|
||||
IconButton(
|
||||
onPressed: fetchLogs,
|
||||
icon: const Icon(Icons.refresh_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.refresh,
|
||||
),
|
||||
const SizedBox(width: 8)
|
||||
]
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
switch (loadStatus) {
|
||||
case 0:
|
||||
return SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.loadingLogs,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
case 1:
|
||||
if (logsData!.data.isNotEmpty) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: fetchLogs,
|
||||
child: ListView.builder(
|
||||
controller: scrollController,
|
||||
padding: const EdgeInsets.only(top: 0),
|
||||
itemCount: isLoadingMore == true
|
||||
? logsData!.data.length+1
|
||||
: logsData!.data.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (isLoadingMore == true && index == logsData!.data.length) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 20),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return LogTile(
|
||||
log: logsData!.data[index],
|
||||
index: index,
|
||||
length: logsData!.data.length,
|
||||
useAlwaysNormalTile: true,
|
||||
onLogTap: (log) => {
|
||||
if (width > 700) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => LogDetailsScreen(
|
||||
log: log,
|
||||
dialog: true
|
||||
)
|
||||
)
|
||||
}
|
||||
else {
|
||||
Navigator.of(context).push(
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
case 2:
|
||||
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();
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
63
lib/screens/clients/client/settings_tile.dart
Normal file
63
lib/screens/clients/client/settings_tile.dart
Normal file
|
@ -0,0 +1,63 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class SettingsTile extends StatelessWidget {
|
||||
final String label;
|
||||
final bool? value;
|
||||
final void Function(bool)? onChange;
|
||||
final bool useGlobalSettingsFiltering;
|
||||
|
||||
const SettingsTile({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.value,
|
||||
this.onChange,
|
||||
required this.useGlobalSettingsFiltering
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onChange != null
|
||||
? value != null ? () => onChange!(!value!) : null
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 6),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
useGlobalSettingsFiltering == false
|
||||
? Switch(
|
||||
value: value!,
|
||||
onChanged: onChange,
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 14,
|
||||
horizontal: 12
|
||||
),
|
||||
child: Text(
|
||||
"Global",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -7,11 +7,11 @@ class TagsModal extends StatefulWidget {
|
|||
final void Function(List<String>) onConfirm;
|
||||
|
||||
const TagsModal({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.selectedTags,
|
||||
required this.tags,
|
||||
required this.onConfirm,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<TagsModal> createState() => _TagsModalState();
|
||||
|
@ -82,12 +82,14 @@ class _TagsModalState extends State<TagsModal> {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
widget.tags[index],
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.tags[index],
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
Checkbox(
|
63
lib/screens/clients/client/tags_section.dart
Normal file
63
lib/screens/clients/client/tags_section.dart
Normal file
|
@ -0,0 +1,63 @@
|
|||
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class TagsSection extends StatelessWidget {
|
||||
final List<String> selectedTags;
|
||||
final void Function(List<String>) onTagsSelected;
|
||||
|
||||
const TagsSection({
|
||||
super.key,
|
||||
required this.selectedTags,
|
||||
required this.onTagsSelected
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => openTagsModal(
|
||||
context: context,
|
||||
selectedTags: selectedTags,
|
||||
onSelectedTags: onTagsSelected
|
||||
) ,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 0, horizontal: 16
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.label_rounded,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.selectTags,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
selectedTags.isNotEmpty
|
||||
? "${selectedTags.length} ${AppLocalizations.of(context)!.tagsSelected}"
|
||||
: AppLocalizations.of(context)!.noTagsSelected,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
112
lib/screens/clients/client/upstream_servers_section.dart
Normal file
112
lib/screens/clients/client/upstream_servers_section.dart
Normal file
|
@ -0,0 +1,112 @@
|
|||
import 'package:adguard_home_manager/screens/clients/client/client_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/section_label.dart';
|
||||
|
||||
class UpstreamServersSection extends StatefulWidget {
|
||||
final List<ControllerListItem> upstreamServers;
|
||||
final void Function() onCheckValidValues;
|
||||
final void Function(List<ControllerListItem>) onUpdateUpstreamServers;
|
||||
|
||||
const UpstreamServersSection({
|
||||
super.key,
|
||||
required this.upstreamServers,
|
||||
required this.onCheckValidValues,
|
||||
required this.onUpdateUpstreamServers
|
||||
});
|
||||
|
||||
@override
|
||||
State<UpstreamServersSection> createState() => _UpstreamServersSectionState();
|
||||
}
|
||||
|
||||
class _UpstreamServersSectionState extends State<UpstreamServersSection> {
|
||||
final Uuid uuid = const Uuid();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.upstreamServers,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12),
|
||||
child: IconButton(
|
||||
onPressed: () => setState(() => widget.upstreamServers.add(
|
||||
ControllerListItem(
|
||||
id: uuid.v4(),
|
||||
controller: TextEditingController()
|
||||
)
|
||||
)),
|
||||
icon: const Icon(Icons.add)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (widget.upstreamServers.isNotEmpty) ...widget.upstreamServers.map((controller) => Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: controller.controller,
|
||||
onChanged: (_) => widget.onCheckValidValues,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.dns_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
labelText: AppLocalizations.of(context)!.serverAddress,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
IconButton(
|
||||
onPressed: () => widget.onUpdateUpstreamServers(
|
||||
widget.upstreamServers.where((e) => e.id != controller.id).toList()
|
||||
),
|
||||
icon: const Icon(Icons.remove_circle_outline_outlined)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
if (widget.upstreamServers.isEmpty) Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.noUpstreamServers,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.willBeUsedGeneralServers,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,838 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/remove_client_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/safe_search_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/services_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/tags_modal.dart';
|
||||
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/compare_versions.dart';
|
||||
import 'package:adguard_home_manager/models/safe_search.dart';
|
||||
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||
import 'package:adguard_home_manager/models/clients.dart';
|
||||
|
||||
class ClientScreen extends StatefulWidget {
|
||||
final Client? client;
|
||||
final String serverVersion;
|
||||
final void Function(Client) onConfirm;
|
||||
final void Function(Client)? onDelete;
|
||||
final bool dialog;
|
||||
|
||||
const ClientScreen({
|
||||
Key? key,
|
||||
this.client,
|
||||
required this.serverVersion,
|
||||
required this.onConfirm,
|
||||
this.onDelete,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ClientScreen> createState() => _ClientScreenState();
|
||||
}
|
||||
|
||||
class _ClientScreenState extends State<ClientScreen> {
|
||||
final Uuid uuid = const Uuid();
|
||||
bool editMode = true;
|
||||
|
||||
bool validValues = false;
|
||||
|
||||
TextEditingController nameController = TextEditingController();
|
||||
|
||||
List<String> selectedTags = [];
|
||||
|
||||
List<Map<dynamic, dynamic>> identifiersControllers = [
|
||||
{
|
||||
'id': 0,
|
||||
'controller': TextEditingController()
|
||||
}
|
||||
];
|
||||
|
||||
bool useGlobalSettingsFiltering = true;
|
||||
bool? enableFiltering;
|
||||
bool? enableSafeBrowsing;
|
||||
bool? enableParentalControl;
|
||||
bool? enableSafeSearch;
|
||||
SafeSearch? safeSearch;
|
||||
|
||||
final SafeSearch defaultSafeSearch = SafeSearch(
|
||||
enabled: false,
|
||||
bing: false,
|
||||
duckduckgo: false,
|
||||
google: false,
|
||||
pixabay: false,
|
||||
yandex: false,
|
||||
youtube: false
|
||||
);
|
||||
|
||||
bool useGlobalSettingsServices = true;
|
||||
List<String> blockedServices = [];
|
||||
|
||||
List<Map<dynamic, dynamic>> upstreamServers = [];
|
||||
|
||||
|
||||
void checkValidValues() {
|
||||
if (
|
||||
nameController.text != '' &&
|
||||
identifiersControllers.isNotEmpty &&
|
||||
identifiersControllers[0]['controller'].text != ''
|
||||
) {
|
||||
setState(() => validValues = true);
|
||||
}
|
||||
else {
|
||||
setState(() => validValues = false);
|
||||
}
|
||||
}
|
||||
|
||||
bool version = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
version = serverVersionIsAhead(
|
||||
currentVersion: widget.serverVersion,
|
||||
referenceVersion: 'v0.107.28',
|
||||
referenceVersionBeta: 'v0.108.0-b.33'
|
||||
);
|
||||
|
||||
if (widget.client != null) {
|
||||
editMode = false;
|
||||
|
||||
validValues = true;
|
||||
|
||||
nameController.text = widget.client!.name;
|
||||
selectedTags = widget.client!.tags;
|
||||
identifiersControllers = widget.client!.ids.map((e) => {
|
||||
'id': uuid.v4(),
|
||||
'controller': TextEditingController(text: e)
|
||||
}).toList();
|
||||
useGlobalSettingsFiltering = widget.client!.useGlobalSettings;
|
||||
enableFiltering = widget.client!.filteringEnabled;
|
||||
enableParentalControl = widget.client!.parentalEnabled;
|
||||
enableSafeBrowsing = widget.client!.safebrowsingEnabled;
|
||||
if (version == true) {
|
||||
safeSearch = widget.client!.safeSearch;
|
||||
}
|
||||
else {
|
||||
enableSafeSearch = widget.client!.safesearchEnabled ?? false;
|
||||
}
|
||||
useGlobalSettingsServices = widget.client!.useGlobalBlockedServices;
|
||||
blockedServices = widget.client!.blockedServices;
|
||||
upstreamServers = widget.client!.upstreams.map((e) => {
|
||||
'id': uuid.v4(),
|
||||
'controller': TextEditingController(text: e)
|
||||
}).toList();
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final clientsProvider = Provider.of<ClientsProvider>(context);
|
||||
final statusProvider = Provider.of<StatusProvider>(context);
|
||||
|
||||
void createClient() {
|
||||
final Client client = Client(
|
||||
name: nameController.text,
|
||||
ids: List<String>.from(identifiersControllers.map((e) => e['controller'].text)),
|
||||
useGlobalSettings: useGlobalSettingsFiltering,
|
||||
filteringEnabled: enableFiltering ?? false,
|
||||
parentalEnabled: enableParentalControl ?? false,
|
||||
safebrowsingEnabled: enableSafeBrowsing ?? false,
|
||||
safesearchEnabled: version == false ? enableSafeSearch : null,
|
||||
safeSearch: version == true ? safeSearch : null,
|
||||
useGlobalBlockedServices: useGlobalSettingsServices,
|
||||
blockedServices: blockedServices,
|
||||
upstreams: List<String>.from(upstreamServers.map((e) => e['controller'].text)),
|
||||
tags: selectedTags
|
||||
);
|
||||
widget.onConfirm(client);
|
||||
}
|
||||
|
||||
Widget sectionLabel({
|
||||
required String label,
|
||||
EdgeInsets? padding
|
||||
}) {
|
||||
return Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(
|
||||
vertical: 24,
|
||||
horizontal: 24
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void enableDisableGlobalSettingsFiltering() {
|
||||
if (useGlobalSettingsFiltering == true) {
|
||||
setState(() {
|
||||
useGlobalSettingsFiltering = false;
|
||||
|
||||
enableFiltering = false;
|
||||
enableSafeBrowsing = false;
|
||||
enableParentalControl = false;
|
||||
enableSafeSearch = false;
|
||||
safeSearch = defaultSafeSearch;
|
||||
});
|
||||
}
|
||||
else if (useGlobalSettingsFiltering == false) {
|
||||
setState(() {
|
||||
useGlobalSettingsFiltering = true;
|
||||
|
||||
enableFiltering = null;
|
||||
enableSafeBrowsing = null;
|
||||
enableParentalControl = null;
|
||||
enableSafeSearch = null;
|
||||
safeSearch = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void openTagsModal() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => TagsModal(
|
||||
selectedTags: selectedTags,
|
||||
tags: clientsProvider.clients!.supportedTags,
|
||||
onConfirm: (selected) => setState(() => selectedTags = selected),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void openServicesModal() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ServicesModal(
|
||||
blockedServices: blockedServices,
|
||||
onConfirm: (values) => setState(() => blockedServices = values),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void updateServicesGlobalSettings(bool value) {
|
||||
if (value == true) {
|
||||
setState(() {
|
||||
blockedServices = [];
|
||||
useGlobalSettingsServices = true;
|
||||
});
|
||||
}
|
||||
else if (value == false) {
|
||||
setState(() {
|
||||
useGlobalSettingsServices = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void openDeleteClientScreen() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => RemoveClientModal(
|
||||
onConfirm: () {
|
||||
Navigator.pop(context);
|
||||
widget.onDelete!(widget.client!);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void openSafeSearchModal() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => SafeSearchModal(
|
||||
safeSearch: safeSearch ?? defaultSafeSearch,
|
||||
disabled: !editMode,
|
||||
onConfirm: (s) => setState(() => safeSearch = s)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget settignsTile({
|
||||
required String label,
|
||||
required bool? value,
|
||||
void Function(bool)? onChange
|
||||
}) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onChange != null
|
||||
? value != null ? () => onChange(!value) : null
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 42,
|
||||
vertical: 5
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
useGlobalSettingsFiltering == false
|
||||
? Switch(
|
||||
value: value!,
|
||||
onChanged: onChange,
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 14,
|
||||
horizontal: 12
|
||||
),
|
||||
child: Text(
|
||||
"Global",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget content(bool withPaddingTop) {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.only(top: 0),
|
||||
children: [
|
||||
if (withPaddingTop == true) const SizedBox(height: 24),
|
||||
if (withPaddingTop == false) const SizedBox(height: 6),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: TextFormField(
|
||||
enabled: widget.client != null ? false : true,
|
||||
controller: nameController,
|
||||
onChanged: (_) => checkValidValues(),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.badge_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
labelText: AppLocalizations.of(context)!.name,
|
||||
),
|
||||
),
|
||||
),
|
||||
sectionLabel(label: AppLocalizations.of(context)!.tags),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: editMode == true ? () => openTagsModal() : null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 0, horizontal: 24
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.label_rounded,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.selectTags,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
selectedTags.isNotEmpty
|
||||
? "${selectedTags.length} ${AppLocalizations.of(context)!.tagsSelected}"
|
||||
: AppLocalizations.of(context)!.noTagsSelected,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
sectionLabel(
|
||||
label: AppLocalizations.of(context)!.identifiers,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24, right: 24, top: 24, bottom: 12
|
||||
)
|
||||
),
|
||||
if (editMode == true) Padding(
|
||||
padding: const EdgeInsets.only(right: 20),
|
||||
child: IconButton(
|
||||
onPressed: () => setState(() => identifiersControllers.add(
|
||||
Map<String, Object>.from({
|
||||
'id': uuid.v4(),
|
||||
'controller': TextEditingController()
|
||||
})
|
||||
)),
|
||||
icon: const Icon(Icons.add)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (identifiersControllers.isNotEmpty) ...identifiersControllers.map((controller) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
enabled: editMode,
|
||||
controller: controller['controller'],
|
||||
onChanged: (_) => checkValidValues(),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.tag),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
helperText: AppLocalizations.of(context)!.identifierHelper,
|
||||
labelText: AppLocalizations.of(context)!.identifier,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (editMode == true) ...[
|
||||
const SizedBox(width: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 25),
|
||||
child: IconButton(
|
||||
onPressed: () => setState(
|
||||
() => identifiersControllers = identifiersControllers.where((e) => e['id'] != controller['id']).toList()
|
||||
),
|
||||
icon: const Icon(Icons.remove_circle_outline_outlined)
|
||||
),
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
)).toList(),
|
||||
if (identifiersControllers.isEmpty) Container(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.noIdentifiers,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
sectionLabel(
|
||||
label: AppLocalizations.of(context)!.settings,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24, right: 24, top: 12, bottom: 24
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: InkWell(
|
||||
onTap: editMode
|
||||
? () => enableDisableGlobalSettingsFiltering()
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 5
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.useGlobalSettings,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: useGlobalSettingsFiltering,
|
||||
onChanged: editMode == true
|
||||
? (value) => enableDisableGlobalSettingsFiltering()
|
||||
: null,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
settignsTile(
|
||||
label: AppLocalizations.of(context)!.enableFiltering,
|
||||
value: enableFiltering,
|
||||
onChange: editMode == true
|
||||
? (value) => setState(() => enableFiltering = value)
|
||||
: null
|
||||
),
|
||||
settignsTile(
|
||||
label: AppLocalizations.of(context)!.enableSafeBrowsing,
|
||||
value: enableSafeBrowsing,
|
||||
onChange: editMode == true
|
||||
? (value) => setState(() => enableSafeBrowsing = value)
|
||||
: null
|
||||
),
|
||||
settignsTile(
|
||||
label: AppLocalizations.of(context)!.enableParentalControl,
|
||||
value: enableParentalControl,
|
||||
onChange: editMode == true
|
||||
? (value) => setState(() => enableParentalControl = value)
|
||||
: null
|
||||
),
|
||||
if (
|
||||
serverVersionIsAhead(
|
||||
currentVersion: statusProvider.serverStatus!.serverVersion,
|
||||
referenceVersion: 'v0.107.28',
|
||||
referenceVersionBeta: 'v0.108.0-b.33'
|
||||
) == true
|
||||
) CustomListTile(
|
||||
title: AppLocalizations.of(context)!.safeSearch,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 42,
|
||||
vertical: 16
|
||||
),
|
||||
trailing: Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: useGlobalSettingsFiltering == true
|
||||
? Colors.grey
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
onTap: useGlobalSettingsFiltering == false
|
||||
? () => openSafeSearchModal()
|
||||
: null,
|
||||
),
|
||||
if (
|
||||
serverVersionIsAhead(
|
||||
currentVersion: statusProvider.serverStatus!.serverVersion,
|
||||
referenceVersion: 'v0.107.28',
|
||||
referenceVersionBeta: 'v0.108.0-b.33'
|
||||
) == false
|
||||
) settignsTile(
|
||||
label: AppLocalizations.of(context)!.enableSafeSearch,
|
||||
value: enableSafeSearch,
|
||||
onChange: editMode == true
|
||||
? (value) => setState(() => enableSafeSearch = value)
|
||||
: null
|
||||
),
|
||||
sectionLabel(label: AppLocalizations.of(context)!.blockedServices),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: InkWell(
|
||||
onTap: editMode == true
|
||||
? () => updateServicesGlobalSettings(!useGlobalSettingsServices)
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 5
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.useGlobalSettings,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: useGlobalSettingsServices,
|
||||
onChanged: editMode == true
|
||||
? (value) => updateServicesGlobalSettings(value)
|
||||
: null,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: editMode == true
|
||||
? useGlobalSettingsServices == false
|
||||
? openServicesModal
|
||||
: null
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 24
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.public,
|
||||
color: useGlobalSettingsServices == false
|
||||
? Theme.of(context).listTileTheme.iconColor
|
||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.38),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.selectBlockedServices,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: useGlobalSettingsServices == false
|
||||
? Theme.of(context).colorScheme.onSurface
|
||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.38),
|
||||
),
|
||||
),
|
||||
if (useGlobalSettingsServices == false) ...[
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
blockedServices.isNotEmpty
|
||||
? "${blockedServices.length} ${AppLocalizations.of(context)!.servicesBlocked}"
|
||||
: AppLocalizations.of(context)!.noBlockedServicesSelected,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
)
|
||||
]
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
sectionLabel(label: AppLocalizations.of(context)!.upstreamServers),
|
||||
if (editMode == true) Padding(
|
||||
padding: const EdgeInsets.only(right: 20),
|
||||
child: IconButton(
|
||||
onPressed: () => setState(() => upstreamServers.add(
|
||||
Map<String, Object>.from({
|
||||
'id': uuid.v4(),
|
||||
'controller': TextEditingController()
|
||||
})
|
||||
)),
|
||||
icon: const Icon(Icons.add)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (upstreamServers.isNotEmpty) ...upstreamServers.map((controller) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
enabled: editMode,
|
||||
controller: controller['controller'],
|
||||
onChanged: (_) => checkValidValues(),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.dns_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
labelText: AppLocalizations.of(context)!.serverAddress,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (editMode == true) ...[
|
||||
const SizedBox(width: 20),
|
||||
IconButton(
|
||||
onPressed: () => setState(
|
||||
() => upstreamServers = upstreamServers.where((e) => e['id'] != controller['id']).toList()
|
||||
),
|
||||
icon: const Icon(Icons.remove_circle_outline_outlined)
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
)).toList(),
|
||||
if (upstreamServers.isEmpty) Container(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.noUpstreamServers,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.willBeUsedGeneralServers,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.dialog == true) {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.clear_rounded)
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
widget.client != null
|
||||
? AppLocalizations.of(context)!.client
|
||||
: AppLocalizations.of(context)!.addClient,
|
||||
style: const TextStyle(
|
||||
fontSize: 22
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
if (widget.client == null || (widget.client != null && editMode == true)) IconButton(
|
||||
onPressed: validValues == true
|
||||
? () {
|
||||
createClient();
|
||||
Navigator.pop(context);
|
||||
}
|
||||
: null,
|
||||
icon: Icon(
|
||||
widget.client != null && editMode == true
|
||||
? Icons.save_rounded
|
||||
: Icons.check_rounded
|
||||
),
|
||||
tooltip: widget.client != null && editMode == true
|
||||
? AppLocalizations.of(context)!.save
|
||||
: AppLocalizations.of(context)!.confirm,
|
||||
),
|
||||
if (widget.client != null && editMode == false) IconButton(
|
||||
onPressed: () => setState(() => editMode = true),
|
||||
icon: const Icon(Icons.edit_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.edit,
|
||||
),
|
||||
if (widget.client != null) IconButton(
|
||||
onPressed: openDeleteClientScreen,
|
||||
icon: const Icon(Icons.delete_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.delete,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: content(false)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.close)
|
||||
),
|
||||
title: Text(
|
||||
widget.client != null
|
||||
? AppLocalizations.of(context)!.client
|
||||
: AppLocalizations.of(context)!.addClient
|
||||
),
|
||||
actions: [
|
||||
if (widget.client == null || (widget.client != null && editMode == true)) IconButton(
|
||||
onPressed: validValues == true
|
||||
? () {
|
||||
createClient();
|
||||
Navigator.pop(context);
|
||||
}
|
||||
: null,
|
||||
icon: Icon(
|
||||
widget.client != null && editMode == true
|
||||
? Icons.save_rounded
|
||||
: Icons.check_rounded
|
||||
),
|
||||
tooltip: widget.client != null && editMode == true
|
||||
? AppLocalizations.of(context)!.save
|
||||
: AppLocalizations.of(context)!.confirm,
|
||||
),
|
||||
if (widget.client != null && editMode == false) IconButton(
|
||||
onPressed: () => setState(() => editMode = true),
|
||||
icon: const Icon(Icons.edit_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.edit,
|
||||
),
|
||||
if (widget.client != null) IconButton(
|
||||
onPressed: openDeleteClientScreen,
|
||||
icon: const Icon(Icons.delete_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.delete,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
),
|
||||
body: content(true)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,256 +1,57 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_split_view/flutter_split_view.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/clients_list.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/search_clients.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/logs_list_client.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/clients_desktop_view.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/added_list.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/clients_lists.dart';
|
||||
|
||||
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/models/clients.dart';
|
||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||
|
||||
class Clients extends StatefulWidget {
|
||||
const Clients({Key? key}) : super(key: key);
|
||||
const Clients({super.key});
|
||||
|
||||
@override
|
||||
State<Clients> createState() => _ClientsState();
|
||||
}
|
||||
|
||||
class _ClientsState extends State<Clients> with TickerProviderStateMixin {
|
||||
late TabController tabController;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
bool searchMode = false;
|
||||
final TextEditingController searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final clientsProvider = Provider.of<ClientsProvider>(context, listen: false);
|
||||
clientsProvider.fetchClients(updateLoading: true);
|
||||
|
||||
super.initState();
|
||||
tabController = TabController(
|
||||
initialIndex: 0,
|
||||
length: 2,
|
||||
vsync: this,
|
||||
);
|
||||
tabController.addListener(
|
||||
() => Provider.of<AppConfigProvider>(context, listen: false).setSelectedClientsTab(tabController.index)
|
||||
);
|
||||
}
|
||||
|
||||
List<AutoClient> generateClientsList(List<AutoClient> clients, List<String> ips) {
|
||||
return clients.where((client) => ips.contains(client.ip)).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final clientsProvider = Provider.of<ClientsProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
PreferredSizeWidget tabBar() {
|
||||
return TabBar(
|
||||
controller: tabController,
|
||||
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.devices),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.activeClients)
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.add_rounded),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.added)
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Widget tabBarView(bool sliver) {
|
||||
return TabBarView(
|
||||
controller: tabController,
|
||||
children: [
|
||||
ClientsList(
|
||||
scrollController: scrollController,
|
||||
data: clientsProvider.loadStatus == LoadStatus.loaded
|
||||
? clientsProvider.filteredActiveClients : [],
|
||||
onClientSelected: (client) => Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => LogsListClient(
|
||||
ip: client.ip,
|
||||
serversProvider: serversProvider,
|
||||
appConfigProvider: appConfigProvider
|
||||
)
|
||||
)),
|
||||
splitView: false,
|
||||
sliver: sliver,
|
||||
),
|
||||
AddedList(
|
||||
scrollController: scrollController,
|
||||
data: clientsProvider.loadStatus == LoadStatus.loaded
|
||||
? clientsProvider.filteredAddedClients : [],
|
||||
onClientSelected: (client) => Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => LogsListClient(
|
||||
ip: client.ids[0],
|
||||
serversProvider: serversProvider,
|
||||
appConfigProvider: appConfigProvider
|
||||
)
|
||||
)),
|
||||
splitView: false,
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if (width > 900) {
|
||||
return SplitView.material(
|
||||
hideDivider: true,
|
||||
flexWidth: const FlexWidth(mainViewFlexWidth: 1, secondaryViewFlexWidth: 2),
|
||||
placeholder: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.selectClientLeftColumn,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||
return Scaffold(
|
||||
body: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
if (constraints.maxWidth > 1000) {
|
||||
return SplitView.material(
|
||||
hideDivider: true,
|
||||
flexWidth: const FlexWidth(mainViewFlexWidth: 1, secondaryViewFlexWidth: 2),
|
||||
placeholder: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.selectClientLeftColumn,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: ClientsDesktopView(
|
||||
serversProvider: serversProvider,
|
||||
appConfigProvider: appConfigProvider,
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
if (!(Platform.isAndroid || Platform.isIOS)) {
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.clients),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
if (clientsProvider.loadStatus == LoadStatus.loaded) ...[
|
||||
IconButton(
|
||||
onPressed: () => {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => const SearchClients()
|
||||
))
|
||||
},
|
||||
icon: const Icon(Icons.search),
|
||||
tooltip: AppLocalizations.of(context)!.searchClients,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
]
|
||||
],
|
||||
bottom: tabBar()
|
||||
),
|
||||
body: tabBarView(false),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: NestedScrollView(
|
||||
controller: scrollController,
|
||||
headerSliverBuilder: ((context, innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverOverlapAbsorber(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: SliverAppBar(
|
||||
title: searchMode == true
|
||||
? Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
searchMode = false;
|
||||
searchController.text = "";
|
||||
clientsProvider.setSearchTermClients(null);
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back_rounded)
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: searchController,
|
||||
onChanged: (value) => clientsProvider.setSearchTermClients(value),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
searchController.text = "";
|
||||
clientsProvider.setSearchTermClients(null);
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.clear_rounded)
|
||||
),
|
||||
hintText: AppLocalizations.of(context)!.search,
|
||||
hintStyle: const TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 18
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 18
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
: Text(AppLocalizations.of(context)!.clients),
|
||||
pinned: true,
|
||||
floating: true,
|
||||
centerTitle: false,
|
||||
forceElevated: innerBoxIsScrolled,
|
||||
actions: [
|
||||
if (clientsProvider.loadStatus == LoadStatus.loaded && searchMode == false) ...[
|
||||
IconButton(
|
||||
onPressed: () => setState(() => searchMode = true),
|
||||
icon: const Icon(Icons.search),
|
||||
tooltip: AppLocalizations.of(context)!.searchClients,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
]
|
||||
],
|
||||
bottom: tabBar()
|
||||
),
|
||||
)
|
||||
];
|
||||
}),
|
||||
body: tabBarView(true)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
child: const ClientsLists(
|
||||
splitView: true,
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
return const ClientsLists(
|
||||
splitView: false,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,244 +0,0 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_split_view/flutter_split_view.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/logs_list_client.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/added_list.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/clients_list.dart';
|
||||
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/models/clients.dart';
|
||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||
|
||||
|
||||
class ClientsDesktopView extends StatefulWidget {
|
||||
final ServersProvider serversProvider;
|
||||
final AppConfigProvider appConfigProvider;
|
||||
|
||||
const ClientsDesktopView({
|
||||
Key? key,
|
||||
required this.serversProvider,
|
||||
required this.appConfigProvider,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ClientsDesktopView> createState() => _ClientsDesktopViewState();
|
||||
}
|
||||
|
||||
class _ClientsDesktopViewState extends State<ClientsDesktopView> with TickerProviderStateMixin {
|
||||
late TabController tabController;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
AutoClient? selectedActiveClient;
|
||||
Client? selectedAddedClient;
|
||||
|
||||
bool searchMode = false;
|
||||
final TextEditingController searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
tabController = TabController(
|
||||
initialIndex: 0,
|
||||
length: 2,
|
||||
vsync: this,
|
||||
);
|
||||
tabController.addListener(() => widget.appConfigProvider.setSelectedClientsTab(tabController.index));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final clientsProvider = Provider.of<ClientsProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
PreferredSizeWidget tabBar() {
|
||||
return TabBar(
|
||||
controller: tabController,
|
||||
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.devices),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.activeClients)
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.add_rounded),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.added)
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Widget tabBarView(bool sliver) {
|
||||
return TabBarView(
|
||||
controller: tabController,
|
||||
children: [
|
||||
ClientsList(
|
||||
scrollController: scrollController,
|
||||
data: clientsProvider.loadStatus == LoadStatus.loaded
|
||||
? clientsProvider.filteredActiveClients : [],
|
||||
onClientSelected: (client) => setState(() {
|
||||
selectedAddedClient = null;
|
||||
selectedActiveClient = client;
|
||||
SplitView.of(context).setSecondary(
|
||||
LogsListClient(
|
||||
ip: client.ip,
|
||||
name: client.name,
|
||||
serversProvider: serversProvider,
|
||||
appConfigProvider: appConfigProvider,
|
||||
)
|
||||
);
|
||||
}),
|
||||
selectedClient: selectedActiveClient,
|
||||
splitView: true,
|
||||
sliver: sliver,
|
||||
),
|
||||
AddedList(
|
||||
scrollController: scrollController,
|
||||
data: clientsProvider.loadStatus == LoadStatus.loaded
|
||||
? clientsProvider.filteredAddedClients : [],
|
||||
onClientSelected: (client) => setState(() {
|
||||
selectedActiveClient = null;
|
||||
selectedAddedClient = client;
|
||||
SplitView.of(context).setSecondary(
|
||||
LogsListClient(
|
||||
ip: client.ids[0],
|
||||
name: client.name,
|
||||
serversProvider: serversProvider,
|
||||
appConfigProvider: appConfigProvider,
|
||||
)
|
||||
);
|
||||
}),
|
||||
selectedClient: selectedAddedClient,
|
||||
splitView: true,
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Widget title() {
|
||||
if (searchMode == true) {
|
||||
return Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
searchMode = false;
|
||||
searchController.text = "";
|
||||
clientsProvider.setSearchTermClients(null);
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back_rounded)
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: searchController,
|
||||
onChanged: (value) => clientsProvider.setSearchTermClients(value),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
searchController.text = "";
|
||||
clientsProvider.setSearchTermClients(null);
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.clear_rounded)
|
||||
),
|
||||
hintText: AppLocalizations.of(context)!.search,
|
||||
hintStyle: const TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 18
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 18
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Text(AppLocalizations.of(context)!.clients);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(Platform.isAndroid || Platform.isIOS)) {
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: title(),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
if (clientsProvider.loadStatus == LoadStatus.loaded && searchMode == false) ...[
|
||||
IconButton(
|
||||
onPressed: () => setState(() => searchMode = true),
|
||||
icon: const Icon(Icons.search),
|
||||
tooltip: AppLocalizations.of(context)!.searchClients,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
]
|
||||
],
|
||||
bottom: tabBar()
|
||||
),
|
||||
body: tabBarView(false),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: NestedScrollView(
|
||||
controller: scrollController,
|
||||
headerSliverBuilder: ((context, innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverOverlapAbsorber(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: SliverAppBar(
|
||||
title: title(),
|
||||
pinned: true,
|
||||
floating: true,
|
||||
centerTitle: false,
|
||||
forceElevated: innerBoxIsScrolled,
|
||||
actions: [
|
||||
if (clientsProvider.loadStatus == LoadStatus.loaded && searchMode == false) ...[
|
||||
IconButton(
|
||||
onPressed: () => setState(() => searchMode = true),
|
||||
icon: const Icon(Icons.search),
|
||||
tooltip: AppLocalizations.of(context)!.searchClients,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
]
|
||||
],
|
||||
bottom: tabBar()
|
||||
),
|
||||
)
|
||||
];
|
||||
}),
|
||||
body: tabBarView(true)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/active_client_tile.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/active_client_tile.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/tab_content_list.dart';
|
||||
|
||||
|
@ -10,22 +10,18 @@ import 'package:adguard_home_manager/providers/clients_provider.dart';
|
|||
import 'package:adguard_home_manager/models/clients.dart';
|
||||
|
||||
class ClientsList extends StatelessWidget {
|
||||
final ScrollController scrollController;
|
||||
final List<AutoClient> data;
|
||||
final void Function(AutoClient) onClientSelected;
|
||||
final AutoClient? selectedClient;
|
||||
final bool splitView;
|
||||
final bool sliver;
|
||||
|
||||
const ClientsList({
|
||||
Key? key,
|
||||
required this.scrollController,
|
||||
super.key,
|
||||
required this.data,
|
||||
required this.onClientSelected,
|
||||
this.selectedClient,
|
||||
required this.splitView,
|
||||
required this.sliver
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -35,7 +31,6 @@ class ClientsList extends StatelessWidget {
|
|||
listPadding: splitView == true
|
||||
? const EdgeInsets.only(top: 8)
|
||||
: null,
|
||||
noSliver: !sliver,
|
||||
loadingGenerator: () => SizedBox(
|
||||
width: double.maxFinite,
|
||||
height: MediaQuery.of(context).size.height-171,
|
||||
|
|
226
lib/screens/clients/clients_lists.dart
Normal file
226
lib/screens/clients/clients_lists.dart
Normal file
|
@ -0,0 +1,226 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_split_view/flutter_split_view.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/added_list.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/clients_list.dart';
|
||||
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/functions/desktop_mode.dart';
|
||||
import 'package:adguard_home_manager/models/clients.dart';
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||
|
||||
class ClientsLists extends StatefulWidget {
|
||||
final bool splitView;
|
||||
|
||||
const ClientsLists({
|
||||
super.key,
|
||||
required this.splitView,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ClientsLists> createState() => _ClientsListsState();
|
||||
}
|
||||
|
||||
class _ClientsListsState extends State<ClientsLists> with TickerProviderStateMixin {
|
||||
late TabController tabController;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
bool searchMode = false;
|
||||
final TextEditingController searchController = TextEditingController();
|
||||
|
||||
AutoClient? _selectedAutoClient;
|
||||
Client? _selectedClient;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final clientsProvider = Provider.of<ClientsProvider>(context, listen: false);
|
||||
clientsProvider.fetchClients(updateLoading: true);
|
||||
|
||||
super.initState();
|
||||
tabController = TabController(
|
||||
initialIndex: 0,
|
||||
length: 2,
|
||||
vsync: this,
|
||||
);
|
||||
tabController.addListener(
|
||||
() => Provider.of<AppConfigProvider>(context, listen: false).setSelectedClientsTab(tabController.index)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final clientsProvider = Provider.of<ClientsProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
void onAutoClientSelected(AutoClient client) {
|
||||
setState(() => _selectedAutoClient = client);
|
||||
final w = LogsListClient(
|
||||
ip: client.ip,
|
||||
serversProvider: serversProvider,
|
||||
appConfigProvider: appConfigProvider,
|
||||
splitView: widget.splitView,
|
||||
);
|
||||
if (widget.splitView) {
|
||||
SplitView.of(context).push(w);
|
||||
}
|
||||
else {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => w,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void onClientSelected(Client client) {
|
||||
setState(() => _selectedClient = client);
|
||||
final w = LogsListClient(
|
||||
ip: client.ids[0],
|
||||
serversProvider: serversProvider,
|
||||
appConfigProvider: appConfigProvider,
|
||||
splitView: widget.splitView,
|
||||
);
|
||||
if (widget.splitView) {
|
||||
SplitView.of(context).push(w);
|
||||
}
|
||||
else {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => w,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: NestedScrollView(
|
||||
headerSliverBuilder: ((context, innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverOverlapAbsorber(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: SliverAppBar(
|
||||
title: searchMode == true
|
||||
? Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
searchMode = false;
|
||||
searchController.text = "";
|
||||
clientsProvider.setSearchTermClients(null);
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.exitSearch,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: searchController,
|
||||
onChanged: (value) => clientsProvider.setSearchTermClients(value),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
searchController.text = "";
|
||||
clientsProvider.setSearchTermClients(null);
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.clear_rounded)
|
||||
),
|
||||
hintText: AppLocalizations.of(context)!.search,
|
||||
hintStyle: const TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 18
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 18
|
||||
),
|
||||
autofocus: true,
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
: Text(AppLocalizations.of(context)!.clients),
|
||||
pinned: true,
|
||||
floating: true,
|
||||
centerTitle: false,
|
||||
forceElevated: innerBoxIsScrolled,
|
||||
surfaceTintColor: isDesktop(MediaQuery.of(context).size.width)
|
||||
? Colors.transparent
|
||||
: null,
|
||||
actions: [
|
||||
if (clientsProvider.loadStatus == LoadStatus.loaded && searchMode == false) ...[
|
||||
IconButton(
|
||||
onPressed: () => setState(() => searchMode = true),
|
||||
icon: const Icon(Icons.search),
|
||||
tooltip: AppLocalizations.of(context)!.searchClients,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
]
|
||||
],
|
||||
bottom: TabBar(
|
||||
controller: tabController,
|
||||
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.devices),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.activeClients)
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.add_rounded),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.added)
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
)
|
||||
];
|
||||
}),
|
||||
body: TabBarView(
|
||||
controller: tabController,
|
||||
children: [
|
||||
ClientsList(
|
||||
data: clientsProvider.loadStatus == LoadStatus.loaded
|
||||
? clientsProvider.filteredActiveClients : [],
|
||||
onClientSelected: onAutoClientSelected,
|
||||
selectedClient: _selectedAutoClient,
|
||||
splitView: widget.splitView,
|
||||
),
|
||||
AddedList(
|
||||
scrollController: scrollController,
|
||||
data: clientsProvider.loadStatus == LoadStatus.loaded
|
||||
? clientsProvider.filteredAddedClients : [],
|
||||
onClientSelected: onClientSelected,
|
||||
selectedClient: _selectedClient,
|
||||
splitView: widget.splitView,
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,18 +1,16 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/client_screen.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||
import 'package:adguard_home_manager/models/clients.dart';
|
||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
|
||||
class ClientsFab extends StatelessWidget {
|
||||
|
@ -20,14 +18,14 @@ class ClientsFab extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
final statusProvider = Provider.of<StatusProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
final clientsProvider = Provider.of<ClientsProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void confirmAddClient(Client client) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.addingClient);
|
||||
|
||||
final result = await clientsProvider.addClient(client);
|
||||
|
@ -51,32 +49,21 @@ class ClientsFab extends StatelessWidget {
|
|||
}
|
||||
|
||||
void openAddClient() {
|
||||
if (width > 900 || !(Platform.isAndroid | Platform.isIOS)) {
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (BuildContext context) => ClientScreen(
|
||||
onConfirm: confirmAddClient,
|
||||
serverVersion: statusProvider.serverStatus!.serverVersion,
|
||||
dialog: true,
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (BuildContext context) => ClientScreen(
|
||||
onConfirm: confirmAddClient,
|
||||
serverVersion: statusProvider.serverStatus!.serverVersion,
|
||||
dialog: false,
|
||||
)
|
||||
));
|
||||
}
|
||||
openClientFormModal(
|
||||
context: context,
|
||||
width: width,
|
||||
onConfirm: confirmAddClient
|
||||
);
|
||||
}
|
||||
|
||||
return FloatingActionButton(
|
||||
onPressed: openAddClient,
|
||||
child: const Icon(Icons.add),
|
||||
);
|
||||
if (statusProvider.serverStatus != null) {
|
||||
return FloatingActionButton(
|
||||
onPressed: openAddClient,
|
||||
child: const Icon(Icons.add),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return const SizedBox();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,268 +0,0 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:async/async.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/logs/log_tile.dart';
|
||||
import 'package:adguard_home_manager/screens/logs/log_details_screen.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/logs.dart';
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||
|
||||
class LogsListClient extends StatefulWidget {
|
||||
final String ip;
|
||||
final String? name;
|
||||
final ServersProvider serversProvider;
|
||||
final AppConfigProvider appConfigProvider;
|
||||
|
||||
const LogsListClient({
|
||||
Key? key,
|
||||
required this.ip,
|
||||
this.name,
|
||||
required this.serversProvider,
|
||||
required this.appConfigProvider
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<LogsListClient> createState() => _LogsListClientState();
|
||||
}
|
||||
|
||||
class _LogsListClientState extends State<LogsListClient> {
|
||||
late ScrollController scrollController;
|
||||
|
||||
bool isLoadingMore = false;
|
||||
|
||||
int logsQuantity = 100;
|
||||
int offset = 0;
|
||||
|
||||
int loadStatus = 0;
|
||||
LogsData? logsData;
|
||||
|
||||
String previousIp = "";
|
||||
|
||||
bool showDivider = true;
|
||||
|
||||
CancelableOperation? cancelableRequest;
|
||||
|
||||
Future fetchLogs({
|
||||
int? inOffset,
|
||||
bool? loadingMore,
|
||||
String? responseStatus,
|
||||
String? searchText,
|
||||
}) async {
|
||||
final serversProvider = Provider.of<ServersProvider>(context, listen: false);
|
||||
|
||||
int offst = inOffset ?? offset;
|
||||
|
||||
if (loadingMore != null && loadingMore == true) {
|
||||
setState(() => isLoadingMore = true);
|
||||
}
|
||||
|
||||
if (cancelableRequest != null) cancelableRequest!.cancel();
|
||||
|
||||
cancelableRequest = CancelableOperation.fromFuture(
|
||||
serversProvider.apiClient!.getLogs(
|
||||
count: logsQuantity,
|
||||
offset: offst,
|
||||
search: '"${widget.ip}"'
|
||||
)
|
||||
);
|
||||
|
||||
final result = await cancelableRequest?.value;
|
||||
|
||||
if (result != null) {
|
||||
if (loadingMore != null && loadingMore == true && mounted) {
|
||||
setState(() => isLoadingMore = false);
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
if (result['result'] == 'success') {
|
||||
setState(() => offset = inOffset != null ? inOffset+logsQuantity : offset+logsQuantity);
|
||||
if (loadingMore != null && loadingMore == true && logsData != null) {
|
||||
LogsData newLogsData = result['data'];
|
||||
newLogsData.data = [...logsData!.data, ...result['data'].data];
|
||||
setState(() => logsData = newLogsData);
|
||||
}
|
||||
else {
|
||||
LogsData newLogsData = result['data'];
|
||||
setState(() => logsData = newLogsData);
|
||||
}
|
||||
setState(() => loadStatus = 1);
|
||||
}
|
||||
else {
|
||||
setState(() => loadStatus = 2);
|
||||
widget.appConfigProvider.addLog(result['log']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void scrollListener() {
|
||||
if (scrollController.position.extentAfter < 500 && isLoadingMore == false) {
|
||||
fetchLogs(loadingMore: true);
|
||||
}
|
||||
if (scrollController.position.pixels > 0) {
|
||||
setState(() => showDivider = false);
|
||||
}
|
||||
else {
|
||||
setState(() => showDivider = true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
scrollController = ScrollController()..addListener(scrollListener);
|
||||
fetchLogs(inOffset: 0);
|
||||
setState(() => previousIp = widget.ip);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
if (widget.ip != previousIp) {
|
||||
setState(() => loadStatus = 0);
|
||||
fetchLogs(inOffset: 0);
|
||||
setState(() => previousIp = widget.ip);
|
||||
}
|
||||
|
||||
Widget status() {
|
||||
switch (loadStatus) {
|
||||
case 0:
|
||||
return SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.loadingLogs,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
case 1:
|
||||
if (logsData!.data.isNotEmpty) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: fetchLogs,
|
||||
child: ListView.builder(
|
||||
controller: scrollController,
|
||||
padding: const EdgeInsets.only(top: 0),
|
||||
itemCount: isLoadingMore == true
|
||||
? logsData!.data.length+1
|
||||
: logsData!.data.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (isLoadingMore == true && index == logsData!.data.length) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 20),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return LogTile(
|
||||
log: logsData!.data[index],
|
||||
index: index,
|
||||
length: logsData!.data.length,
|
||||
useAlwaysNormalTile: true,
|
||||
onLogTap: (log) => {
|
||||
if (width > 700) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => LogDetailsScreen(
|
||||
log: log,
|
||||
dialog: true
|
||||
)
|
||||
)
|
||||
}
|
||||
else {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => LogDetailsScreen(
|
||||
log: log,
|
||||
dialog: false
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.noLogsDisplay,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
case 2:
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.name != null && widget.name != '' ? widget.name! : widget.ip),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
if (!(Platform.isAndroid || Platform.isIOS)) ...[
|
||||
IconButton(
|
||||
onPressed: fetchLogs,
|
||||
icon: const Icon(Icons.refresh_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.refresh,
|
||||
),
|
||||
const SizedBox(width: 8)
|
||||
]
|
||||
],
|
||||
),
|
||||
body: status(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/custom_list_tile_dialog.dart';
|
||||
|
||||
class OptionsModal extends StatelessWidget {
|
||||
final void Function() onEdit;
|
||||
final void Function() onDelete;
|
||||
|
||||
const OptionsModal({
|
||||
Key? key,
|
||||
required this.onDelete,
|
||||
required this.onEdit,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 0,
|
||||
vertical: 16
|
||||
),
|
||||
title: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.more_horiz,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.options,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 24),
|
||||
CustomListTileDialog(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
onEdit();
|
||||
},
|
||||
title: AppLocalizations.of(context)!.edit,
|
||||
icon: Icons.edit,
|
||||
),
|
||||
CustomListTileDialog(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
onDelete();
|
||||
},
|
||||
title: AppLocalizations.of(context)!.delete,
|
||||
icon: Icons.delete,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.close)
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,21 +1,19 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/remove_client_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client_screen.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/options_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
|
||||
import 'package:adguard_home_manager/widgets/options_menu.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/section_label.dart';
|
||||
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/menu_option.dart';
|
||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||
import 'package:adguard_home_manager/functions/compare_versions.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/clients_provider.dart';
|
||||
|
@ -23,7 +21,7 @@ import 'package:adguard_home_manager/models/clients.dart';
|
|||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||
|
||||
class SearchClients extends StatefulWidget {
|
||||
const SearchClients({Key? key}) : super(key: key);
|
||||
const SearchClients({super.key});
|
||||
|
||||
@override
|
||||
State<SearchClients> createState() => _SearchClientsState();
|
||||
|
@ -89,7 +87,7 @@ class _SearchClientsState extends State<SearchClients> {
|
|||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void deleteClient(Client client) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.removingClient);
|
||||
|
||||
final result = await clientsProvider.deleteClient(client);
|
||||
|
@ -113,7 +111,7 @@ class _SearchClientsState extends State<SearchClients> {
|
|||
}
|
||||
|
||||
void confirmEditClient(Client client) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.addingClient);
|
||||
|
||||
final result = await clientsProvider.editClient(client);
|
||||
|
@ -137,31 +135,12 @@ class _SearchClientsState extends State<SearchClients> {
|
|||
}
|
||||
|
||||
void openClientModal(Client client) {
|
||||
if (width > 900 || !(Platform.isAndroid | Platform.isIOS)) {
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (BuildContext context) => ClientScreen(
|
||||
onConfirm: confirmEditClient,
|
||||
serverVersion: statusProvider.serverStatus!.serverVersion,
|
||||
onDelete: deleteClient,
|
||||
client: client,
|
||||
dialog: true,
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (BuildContext context) => ClientScreen(
|
||||
onConfirm: confirmEditClient,
|
||||
serverVersion: statusProvider.serverStatus!.serverVersion,
|
||||
onDelete: deleteClient,
|
||||
client: client,
|
||||
dialog: false,
|
||||
)
|
||||
));
|
||||
}
|
||||
openClientFormModal(
|
||||
context: context,
|
||||
width: width,
|
||||
onConfirm: confirmEditClient,
|
||||
onDelete: deleteClient
|
||||
);
|
||||
}
|
||||
|
||||
void openDeleteModal(Client client) {
|
||||
|
@ -173,15 +152,15 @@ class _SearchClientsState extends State<SearchClients> {
|
|||
);
|
||||
}
|
||||
|
||||
void openOptionsModal(Client client) {
|
||||
showModal(
|
||||
context: context,
|
||||
builder: (ctx) => OptionsModal(
|
||||
onDelete: () => openDeleteModal(client),
|
||||
onEdit: () => openClientModal(client),
|
||||
)
|
||||
);
|
||||
}
|
||||
// void openOptionsModal(Client client) {
|
||||
// showModal(
|
||||
// context: context,
|
||||
// builder: (ctx) => OptionsModal(
|
||||
// onDelete: () => openDeleteModal(client),
|
||||
// onEdit: () => openClientModal(client),
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
|
@ -246,92 +225,96 @@ class _SearchClientsState extends State<SearchClients> {
|
|||
primary: false,
|
||||
itemCount: clientsScreen.length,
|
||||
padding: const EdgeInsets.only(bottom: 0),
|
||||
itemBuilder: (context, index) => ListTile(
|
||||
contentPadding: index == 0
|
||||
? const EdgeInsets.only(left: 20, right: 20, bottom: 15)
|
||||
: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
|
||||
isThreeLine: true,
|
||||
onLongPress: () => openOptionsModal(clientsScreen[index]),
|
||||
onTap: () => openClientModal(clientsScreen[index]),
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 5),
|
||||
child: Text(
|
||||
clientsScreen[index].name,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.normal
|
||||
itemBuilder: (context, index) => OptionsMenu(
|
||||
options: (v) => [
|
||||
MenuOption(
|
||||
icon: Icons.edit_rounded,
|
||||
title: AppLocalizations.of(context)!.edit,
|
||||
action: () => openClientModal(v)
|
||||
),
|
||||
MenuOption(
|
||||
icon: Icons.delete_rounded,
|
||||
title: AppLocalizations.of(context)!.delete,
|
||||
action: () => openDeleteModal(v)
|
||||
),
|
||||
],
|
||||
value: clientsScreen[index],
|
||||
child: ListTile(
|
||||
contentPadding: index == 0
|
||||
? const EdgeInsets.only(left: 20, right: 20, bottom: 15)
|
||||
: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
|
||||
isThreeLine: true,
|
||||
onTap: statusProvider.serverStatus != null
|
||||
? () => openClientModal(clientsScreen[index])
|
||||
: null,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 5),
|
||||
child: Text(
|
||||
clientsScreen[index].name,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.normal
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(clientsScreen[index].ids.toString().replaceAll(RegExp(r'^\[|\]$'), '')),
|
||||
const SizedBox(height: 7),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.filter_list_rounded,
|
||||
size: 19,
|
||||
color: clientsScreen[index].filteringEnabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Icon(
|
||||
Icons.vpn_lock_rounded,
|
||||
size: 18,
|
||||
color: clientsScreen[index].safebrowsingEnabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Icon(
|
||||
Icons.block,
|
||||
size: 18,
|
||||
color: clientsScreen[index].parentalEnabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Icon(
|
||||
Icons.search_rounded,
|
||||
size: 19,
|
||||
color: serverVersionIsAhead(
|
||||
currentVersion: statusProvider.serverStatus!.serverVersion,
|
||||
referenceVersion: 'v0.107.28',
|
||||
referenceVersionBeta: 'v0.108.0-b.33'
|
||||
) == true
|
||||
? clientsScreen[index].safeSearch != null && clientsScreen[index].safeSearch!.enabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red
|
||||
: clientsScreen[index].safesearchEnabled == true
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(clientsScreen[index].ids.toString().replaceAll(RegExp(r'^\[|\]$'), '')),
|
||||
const SizedBox(height: 7),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.filter_list_rounded,
|
||||
size: 19,
|
||||
color: clientsScreen[index].filteringEnabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red,
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Icon(
|
||||
Icons.vpn_lock_rounded,
|
||||
size: 18,
|
||||
color: clientsScreen[index].safebrowsingEnabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Icon(
|
||||
Icons.block,
|
||||
size: 18,
|
||||
color: clientsScreen[index].parentalEnabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Icon(
|
||||
Icons.search_rounded,
|
||||
size: 19,
|
||||
color: clientsScreen[index].safeSearch != null && clientsScreen[index].safeSearch!.enabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
|
@ -11,7 +11,7 @@ import 'package:adguard_home_manager/providers/servers_provider.dart';
|
|||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
|
||||
class Connect extends StatefulWidget {
|
||||
const Connect({Key? key}) : super(key: key);
|
||||
const Connect({super.key});
|
||||
|
||||
@override
|
||||
State<Connect> createState() => _ConnectState();
|
||||
|
@ -61,26 +61,28 @@ class _ConnectState extends State<Connect> {
|
|||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.connect),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
ServersList(
|
||||
context: context,
|
||||
controllers: expandableControllerList,
|
||||
onChange: expandOrContract,
|
||||
scrollController: scrollController,
|
||||
breakingWidth: 700,
|
||||
),
|
||||
AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
curve: Curves.easeInOut,
|
||||
bottom: isVisible ?
|
||||
appConfigProvider.showingSnackbar
|
||||
? 70 : 20
|
||||
: -70,
|
||||
right: 20,
|
||||
child: const FabConnect()
|
||||
)
|
||||
],
|
||||
body: SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
ServersList(
|
||||
context: context,
|
||||
controllers: expandableControllerList,
|
||||
onChange: expandOrContract,
|
||||
scrollController: scrollController,
|
||||
breakingWidth: 700,
|
||||
),
|
||||
AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
curve: Curves.easeInOut,
|
||||
bottom: isVisible ?
|
||||
appConfigProvider.showingSnackbar
|
||||
? 90 : 20
|
||||
: -90,
|
||||
right: 20,
|
||||
child: const FabConnect()
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/add_server_modal.dart';
|
||||
import 'package:adguard_home_manager/widgets/version_warning_modal.dart';
|
||||
import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart';
|
||||
|
||||
class FabConnect extends StatelessWidget {
|
||||
const FabConnect({Key? key}) : super(key: key);
|
||||
|
@ -12,37 +11,7 @@ class FabConnect extends StatelessWidget {
|
|||
|
||||
void openAddServerModal() async {
|
||||
await Future.delayed(const Duration(seconds: 0), (() => {
|
||||
if (width > 700) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => AddServerModal(
|
||||
window: true,
|
||||
onUnsupportedVersion: (version) => showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => VersionWarningModal(
|
||||
version: version
|
||||
),
|
||||
barrierDismissible: false
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
else {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (BuildContext context) => AddServerModal(
|
||||
window: false,
|
||||
onUnsupportedVersion: (version) => showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => VersionWarningModal(
|
||||
version: version
|
||||
),
|
||||
barrierDismissible: false
|
||||
),
|
||||
)
|
||||
))
|
||||
}
|
||||
openServerFormModal(context: context, width: width)
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/filters/add_custom_rule.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/add_list_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/modals/add_custom_rule.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/details/add_list_modal.dart';
|
||||
|
||||
import 'package:adguard_home_manager/providers/filtering_provider.dart';
|
||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||
|
@ -32,7 +32,7 @@ class AddFiltersButton extends StatelessWidget {
|
|||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void confirmAddRule(String rule) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.addingRule);
|
||||
|
||||
final result = await filteringProvider.addCustomRule(rule);
|
||||
|
@ -56,31 +56,34 @@ class AddFiltersButton extends StatelessWidget {
|
|||
}
|
||||
|
||||
void openAddCustomRule() {
|
||||
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AddCustomRule(
|
||||
onConfirm: confirmAddRule,
|
||||
dialog: true,
|
||||
),
|
||||
barrierDismissible: false
|
||||
);
|
||||
}
|
||||
else {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => AddCustomRule(
|
||||
onConfirm: confirmAddRule,
|
||||
dialog: false,
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
barrierColor: !(width > 700 || !(Platform.isAndroid || Platform.isIOS))
|
||||
?Colors.transparent
|
||||
: Colors.black54,
|
||||
transitionBuilder: (context, anim1, anim2, child) {
|
||||
return SlideTransition(
|
||||
position: Tween(
|
||||
begin: const Offset(0, 1),
|
||||
end: const Offset(0, 0)
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: anim1,
|
||||
curve: Curves.easeInOutCubicEmphasized
|
||||
)
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
pageBuilder: (context, animation, secondaryAnimation) => AddCustomRule(
|
||||
fullScreen: !(width > 700 || !(Platform.isAndroid || Platform.isIOS)),
|
||||
onConfirm: confirmAddRule,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void confirmAddList({required String name, required String url, required String type}) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.addingList);
|
||||
|
||||
final result = await filteringProvider.addList(name: name, url: url, type: type);
|
||||
|
@ -131,6 +134,7 @@ class AddFiltersButton extends StatelessWidget {
|
|||
else {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
builder: (ctx) => AddListModal(
|
||||
type: type,
|
||||
onConfirm: confirmAddList,
|
||||
|
|
|
@ -1,230 +0,0 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/filtering.dart';
|
||||
|
||||
class AddListModal 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;
|
||||
final bool dialog;
|
||||
|
||||
const AddListModal({
|
||||
Key? key,
|
||||
required this.type,
|
||||
this.list,
|
||||
this.onConfirm,
|
||||
this.onEdit,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AddListModal> createState() => _AddListModalState();
|
||||
}
|
||||
|
||||
class _AddListModalState extends State<AddListModal> {
|
||||
final TextEditingController nameController = TextEditingController();
|
||||
final TextEditingController urlController = TextEditingController();
|
||||
String? urlError;
|
||||
|
||||
bool validData = false;
|
||||
|
||||
void checkValidValues() {
|
||||
if (nameController.text != '' && urlController.text != '') {
|
||||
setState(() => validData = true);
|
||||
}
|
||||
else {
|
||||
setState(() => validData = false);
|
||||
}
|
||||
}
|
||||
|
||||
void validateUrl(String value) {
|
||||
final urlRegex = RegExp(r'^(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})$');
|
||||
if (urlRegex.hasMatch(value)) {
|
||||
setState(() => urlError = null);
|
||||
}
|
||||
else {
|
||||
final pathRegex = RegExp(r'^(((\\|\/)[a-z0-9^&@{}\[\],$=!\-#\(\)%\.\+~_]+)*(\\|\/))([^\\\/:\*\<>\|]+\.[a-z0-9]+)$');
|
||||
if (pathRegex.hasMatch(value)) {
|
||||
setState(() => urlError = null);
|
||||
}
|
||||
else {
|
||||
setState(() => urlError = AppLocalizations.of(context)!.urlNotValid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
if (widget.list != null) {
|
||||
nameController.text = widget.list!.name;
|
||||
urlController.text = widget.list!.url;
|
||||
|
||||
validData = true;
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget content() {
|
||||
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(
|
||||
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: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel)
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
TextButton(
|
||||
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
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (Platform.isIOS) const SizedBox(height: 16)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
topRight: Radius.circular(28)
|
||||
),
|
||||
color: Theme.of(context).dialogBackgroundColor
|
||||
),
|
||||
child: content()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,262 +0,0 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/blocked_services.dart';
|
||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/providers/filtering_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
|
||||
class BlockedServicesScreen extends StatefulWidget {
|
||||
final bool dialog;
|
||||
|
||||
const BlockedServicesScreen({
|
||||
Key? key,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BlockedServicesScreen> createState() => _BlockedServicesScreenStateWidget();
|
||||
}
|
||||
|
||||
class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreen> {
|
||||
List<String> values = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final filteringProvider = Provider.of<FilteringProvider>(context, listen: false);
|
||||
|
||||
if (filteringProvider.blockedServicesLoadStatus != LoadStatus.loaded) {
|
||||
filteringProvider.loadBlockedServices(showLoading: true);
|
||||
}
|
||||
|
||||
values = filteringProvider.filtering!.blockedServices;
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filteringProvider = Provider.of<FilteringProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
void updateValues(bool value, BlockedService item) {
|
||||
if (value == true) {
|
||||
setState(() {
|
||||
values = values.where((v) => v != item.id).toList();
|
||||
});
|
||||
}
|
||||
else {
|
||||
setState(() {
|
||||
values.add(item.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void updateBlockedServices() async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
processModal.open(AppLocalizations.of(context)!.updating);
|
||||
|
||||
final result = await filteringProvider.updateBlockedServices(values);
|
||||
|
||||
processModal.close();
|
||||
|
||||
if (result == true) {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.blockedServicesUpdated,
|
||||
color: Colors.green
|
||||
);
|
||||
}
|
||||
else {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.blockedServicesNotUpdated,
|
||||
color: Colors.red
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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.dialog == true) {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 400
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.clear_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.close,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.blockedServices,
|
||||
style: const TextStyle(
|
||||
fontSize: 22
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
onPressed: updateBlockedServices,
|
||||
icon: const Icon(
|
||||
Icons.save_rounded
|
||||
),
|
||||
tooltip: AppLocalizations.of(context)!.save,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: body()
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.blockedServices),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: updateBlockedServices,
|
||||
icon: const Icon(
|
||||
Icons.save_rounded
|
||||
),
|
||||
tooltip: AppLocalizations.of(context)!.save,
|
||||
),
|
||||
const SizedBox(width: 10)
|
||||
],
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
final result = await filteringProvider.loadBlockedServices();
|
||||
if (result == false) {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.blockedServicesListNotLoaded,
|
||||
color: Colors.red
|
||||
);
|
||||
}
|
||||
},
|
||||
child: body()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,259 +0,0 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
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/providers/servers_provider.dart';
|
||||
|
||||
class CheckHostModal extends StatefulWidget {
|
||||
final bool dialog;
|
||||
|
||||
const CheckHostModal({
|
||||
Key? key,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<CheckHostModal> createState() => _CheckHostModalState();
|
||||
}
|
||||
|
||||
class _CheckHostModalState extends State<CheckHostModal> {
|
||||
final TextEditingController domainController = TextEditingController();
|
||||
String? domainError;
|
||||
|
||||
Widget? resultWidget;
|
||||
|
||||
void validateDomain(String value) {
|
||||
final domainRegex = RegExp(r'^([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+$');
|
||||
if (domainRegex.hasMatch(value)) {
|
||||
setState(() => domainError = null);
|
||||
}
|
||||
else {
|
||||
setState(() => domainError = AppLocalizations.of(context)!.domainNotValid);
|
||||
}
|
||||
}
|
||||
|
||||
Widget checking() {
|
||||
return SizedBox(
|
||||
height: 30,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(width: 20),
|
||||
Text(AppLocalizations.of(context)!.checkingHost)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
void checkHost() async {
|
||||
setState(() => resultWidget = checking());
|
||||
|
||||
final result = await serversProvider.apiClient!.checkHostFiltered(host: domainController.text);
|
||||
|
||||
if (mounted) {
|
||||
if (result['result'] == 'success') {
|
||||
final status = getFilteredStatus(context, appConfigProvider, result['data']['reason'], true);
|
||||
if (mounted) {
|
||||
setState(() => resultWidget = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
status['icon'],
|
||||
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
|
||||
? 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
|
||||
),
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget content() {
|
||||
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.shield_rounded,
|
||||
size: 24,
|
||||
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,
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
257
lib/screens/filters/details/add_list_modal.dart
Normal file
257
lib/screens/filters/details/add_list_modal.dart
Normal file
|
@ -0,0 +1,257 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/filtering.dart';
|
||||
|
||||
class AddListModal extends StatelessWidget {
|
||||
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;
|
||||
final bool dialog;
|
||||
|
||||
const AddListModal({
|
||||
super.key,
|
||||
required this.type,
|
||||
this.list,
|
||||
this.onConfirm,
|
||||
this.onEdit,
|
||||
required this.dialog
|
||||
});
|
||||
|
||||
@override
|
||||
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 _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 urlController = TextEditingController();
|
||||
String? urlError;
|
||||
|
||||
bool validData = false;
|
||||
|
||||
void checkValidValues() {
|
||||
if (nameController.text != '' && urlController.text != '') {
|
||||
setState(() => validData = true);
|
||||
}
|
||||
else {
|
||||
setState(() => validData = false);
|
||||
}
|
||||
}
|
||||
|
||||
void validateUrl(String value) {
|
||||
final urlRegex = RegExp(r'^(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})$');
|
||||
if (urlRegex.hasMatch(value)) {
|
||||
setState(() => urlError = null);
|
||||
}
|
||||
else {
|
||||
final pathRegex = RegExp(r'^(((\\|\/)[a-z0-9^&@{}\[\],$=!\-#\(\)%\.\+~_]+)*(\\|\/))([^\\\/:\*\<>\|]+\.[a-z0-9]+)$');
|
||||
if (pathRegex.hasMatch(value)) {
|
||||
setState(() => urlError = null);
|
||||
}
|
||||
else {
|
||||
setState(() => urlError = AppLocalizations.of(context)!.urlNotValid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
if (widget.list != null) {
|
||||
nameController.text = widget.list!.name;
|
||||
urlController.text = widget.list!.url;
|
||||
|
||||
validData = true;
|
||||
}
|
||||
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(
|
||||
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: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel)
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
TextButton(
|
||||
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
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (Platform.isIOS) const SizedBox(height: 16)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
265
lib/screens/filters/details/check_host_modal.dart
Normal file
265
lib/screens/filters/details/check_host_modal.dart
Normal file
|
@ -0,0 +1,265 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
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/providers/servers_provider.dart';
|
||||
|
||||
class CheckHostModal extends StatelessWidget {
|
||||
final bool dialog;
|
||||
|
||||
const CheckHostModal({
|
||||
super.key,
|
||||
required this.dialog
|
||||
});
|
||||
|
||||
@override
|
||||
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 _Content extends StatefulWidget {
|
||||
const _Content();
|
||||
|
||||
@override
|
||||
State<_Content> createState() => _ContentState();
|
||||
}
|
||||
|
||||
class _ContentState extends State<_Content> {
|
||||
final TextEditingController domainController = TextEditingController();
|
||||
String? domainError;
|
||||
|
||||
Widget? resultWidget;
|
||||
|
||||
void validateDomain(String value) {
|
||||
final domainRegex = RegExp(r'^([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+$');
|
||||
if (domainRegex.hasMatch(value)) {
|
||||
setState(() => domainError = null);
|
||||
}
|
||||
else {
|
||||
setState(() => domainError = AppLocalizations.of(context)!.domainNotValid);
|
||||
}
|
||||
}
|
||||
|
||||
Widget checking() {
|
||||
return SizedBox(
|
||||
height: 30,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(width: 20),
|
||||
Text(AppLocalizations.of(context)!.checkingHost)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
void checkHost() async {
|
||||
setState(() => resultWidget = checking());
|
||||
|
||||
final result = await serversProvider.apiClient2!.checkHostFiltered(host: domainController.text);
|
||||
if (!mounted) return;
|
||||
|
||||
if (result.successful == true) {
|
||||
final status = getFilteredStatus(context, appConfigProvider, result.content['reason'], true);
|
||||
if (mounted) {
|
||||
setState(() => resultWidget = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
status['icon'],
|
||||
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
|
||||
? 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
|
||||
),
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
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.shield_rounded,
|
||||
size: 24,
|
||||
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,
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -7,10 +7,11 @@ import 'package:flutter/rendering.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/filters/add_list_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/delete_list_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/details/add_list_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/modals/delete_list_modal.dart';
|
||||
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/open_url.dart';
|
||||
import 'package:adguard_home_manager/functions/format_time.dart';
|
||||
import 'package:adguard_home_manager/providers/filtering_provider.dart';
|
||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||
|
@ -24,11 +25,11 @@ class ListDetailsScreen extends StatefulWidget {
|
|||
final bool dialog;
|
||||
|
||||
const ListDetailsScreen({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.listId,
|
||||
required this.type,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<ListDetailsScreen> createState() => _ListDetailsScreenState();
|
||||
|
@ -76,15 +77,20 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
|||
// ------- //
|
||||
}
|
||||
|
||||
void updateList(FilteringListActions action) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
void updateList({
|
||||
required FilteringListActions action,
|
||||
required Filter filterList,
|
||||
}) async {
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(
|
||||
list!.enabled == true
|
||||
? AppLocalizations.of(context)!.disablingList
|
||||
: AppLocalizations.of(context)!.enablingList,
|
||||
action == FilteringListActions.edit
|
||||
? AppLocalizations.of(context)!.savingList
|
||||
: action == FilteringListActions.disable
|
||||
? AppLocalizations.of(context)!.disablingList
|
||||
: AppLocalizations.of(context)!.enablingList,
|
||||
);
|
||||
final result = await filteringProvider.updateList(
|
||||
list: list,
|
||||
list: filterList,
|
||||
type: widget.type,
|
||||
action: action
|
||||
);
|
||||
|
@ -153,6 +159,11 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
|||
vertical: 8
|
||||
)
|
||||
: null,
|
||||
trailing: IconButton(
|
||||
onPressed: () => openUrl(list!.url),
|
||||
icon: const Icon(Icons.open_in_browser_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.openListUrl,
|
||||
),
|
||||
),
|
||||
CustomListTile(
|
||||
icon: Icons.list_rounded,
|
||||
|
@ -204,7 +215,8 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
|||
list: list,
|
||||
type: widget.type,
|
||||
onEdit: ({required Filter list, required String type}) async => updateList(
|
||||
FilteringListActions.edit
|
||||
action: FilteringListActions.edit,
|
||||
filterList: list
|
||||
),
|
||||
dialog: true,
|
||||
),
|
||||
|
@ -213,11 +225,13 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
|||
else {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
builder: (ctx) => AddListModal(
|
||||
list: list,
|
||||
type: widget.type,
|
||||
onEdit: ({required Filter list, required String type}) async => updateList(
|
||||
FilteringListActions.edit
|
||||
action: FilteringListActions.edit,
|
||||
filterList: list
|
||||
),
|
||||
dialog: false,
|
||||
),
|
||||
|
@ -235,7 +249,7 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
|||
context: context,
|
||||
builder: (c) => DeleteListModal(
|
||||
onConfirm: () async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.deletingList);
|
||||
final result = await filteringProvider.deleteList(
|
||||
listUrl: list!.url,
|
||||
|
@ -302,9 +316,10 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
|||
children: [
|
||||
IconButton(
|
||||
onPressed: () => updateList(
|
||||
list!.enabled == true
|
||||
action: list!.enabled == true
|
||||
? FilteringListActions.disable
|
||||
: FilteringListActions.enable
|
||||
: FilteringListActions.enable,
|
||||
filterList: list
|
||||
),
|
||||
icon: Icon(
|
||||
list.enabled == true
|
||||
|
@ -343,46 +358,54 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
|||
);
|
||||
}
|
||||
else {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.listDetails),
|
||||
actions: list != null ? actions() : null,
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
if (list != null) ListView(
|
||||
children: content(),
|
||||
return Dialog.fullscreen(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: CloseButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
if (list == null) Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.listNotAvailable,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
title: Text(AppLocalizations.of(context)!.listDetails),
|
||||
actions: list != null ? actions() : null,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
if (list != null) ListView(
|
||||
children: content(),
|
||||
),
|
||||
),
|
||||
if (list == null) Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.listNotAvailable,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (list != null) AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
curve: Curves.easeInOut,
|
||||
bottom: fabVisible ?
|
||||
appConfigProvider.showingSnackbar
|
||||
? 70 : (Platform.isIOS ? 40 : 20)
|
||||
: -70,
|
||||
right: 20,
|
||||
child: FloatingActionButton(
|
||||
onPressed: () => updateList(
|
||||
action: list!.enabled == true
|
||||
? FilteringListActions.disable
|
||||
: FilteringListActions.enable,
|
||||
filterList: list
|
||||
),
|
||||
child: Icon(
|
||||
list.enabled == true
|
||||
? Icons.gpp_bad_rounded
|
||||
: Icons.verified_user_rounded,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (list != null) AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
curve: Curves.easeInOut,
|
||||
bottom: fabVisible ?
|
||||
appConfigProvider.showingSnackbar
|
||||
? 70 : (Platform.isIOS ? 40 : 20)
|
||||
: -70,
|
||||
right: 20,
|
||||
child: FloatingActionButton(
|
||||
onPressed: () => updateList(
|
||||
list!.enabled == true
|
||||
? FilteringListActions.disable
|
||||
: FilteringListActions.enable
|
||||
),
|
||||
child: Icon(
|
||||
list.enabled == true
|
||||
? Icons.gpp_bad_rounded
|
||||
: Icons.verified_user_rounded,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
|
@ -6,13 +6,13 @@ import 'package:flutter/material.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/filters/check_host_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/details/check_host_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/filters_tabs_view.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/filters_triple_column.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/list_details_screen.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/remove_custom_rule_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/blocked_services_screen.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/update_interval_lists_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/details/list_details_screen.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/modals/remove_custom_rule_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/modals/blocked_services_screen.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/modals/update_interval_lists_modal.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||
|
@ -51,13 +51,11 @@ class _FiltersState extends State<Filters> {
|
|||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void updateLists() async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.updatingLists);
|
||||
|
||||
final result = await filteringProvider.updateLists();
|
||||
|
||||
if (!mounted) return;
|
||||
processModal.close();
|
||||
|
||||
if (result['success'] == true) {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
|
@ -87,6 +85,7 @@ class _FiltersState extends State<Filters> {
|
|||
else {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => const CheckHostModal(
|
||||
dialog: false,
|
||||
),
|
||||
|
@ -98,7 +97,7 @@ class _FiltersState extends State<Filters> {
|
|||
}
|
||||
|
||||
void enableDisableFiltering() async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(
|
||||
statusProvider.serverStatus!.filteringEnabled == true
|
||||
? AppLocalizations.of(context)!.disableFiltering
|
||||
|
@ -126,7 +125,7 @@ class _FiltersState extends State<Filters> {
|
|||
}
|
||||
|
||||
void setUpdateFrequency(int value) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.changingUpdateFrequency);
|
||||
|
||||
final result = await filteringProvider.changeUpdateFrequency(value);
|
||||
|
@ -149,31 +148,14 @@ class _FiltersState extends State<Filters> {
|
|||
}
|
||||
}
|
||||
|
||||
void openBlockedServicesModal() {
|
||||
void openBlockedServices() {
|
||||
Future.delayed(const Duration(seconds: 0), () {
|
||||
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => const BlockedServicesScreen(
|
||||
dialog: true,
|
||||
),
|
||||
barrierDismissible: false
|
||||
);
|
||||
}
|
||||
else {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const BlockedServicesScreen(
|
||||
dialog: false,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
openBlockedServicesModal(context: context, width: width);
|
||||
});
|
||||
}
|
||||
|
||||
void removeCustomRule(String rule) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.deletingRule);
|
||||
|
||||
final result = await filteringProvider.removeCustomRule(rule);
|
||||
|
@ -206,28 +188,31 @@ class _FiltersState extends State<Filters> {
|
|||
}
|
||||
|
||||
void openListDetails(Filter filter, String type) {
|
||||
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ListDetailsScreen(
|
||||
listId: filter.id,
|
||||
type: type,
|
||||
dialog: true,
|
||||
),
|
||||
barrierDismissible: false
|
||||
);
|
||||
}
|
||||
else {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ListDetailsScreen(
|
||||
listId: filter.id,
|
||||
type: type,
|
||||
dialog: false,
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
barrierColor: !(width > 900 || !(Platform.isAndroid | Platform.isIOS))
|
||||
?Colors.transparent
|
||||
: Colors.black54,
|
||||
transitionBuilder: (context, anim1, anim2, child) {
|
||||
return SlideTransition(
|
||||
position: Tween(
|
||||
begin: const Offset(0, 1),
|
||||
end: const Offset(0, 0)
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: anim1,
|
||||
curve: Curves.easeInOutCubicEmphasized
|
||||
)
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
pageBuilder: (context, animation, secondaryAnimation) => ListDetailsScreen(
|
||||
listId: filter.id,
|
||||
type: type,
|
||||
dialog: width > 900 || !(Platform.isAndroid | Platform.isIOS),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> actions() {
|
||||
|
@ -286,6 +271,7 @@ class _FiltersState extends State<Filters> {
|
|||
else {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => UpdateIntervalListsModal(
|
||||
interval: filteringProvider.filtering!.interval,
|
||||
onChange: setUpdateFrequency,
|
||||
|
@ -312,7 +298,7 @@ class _FiltersState extends State<Filters> {
|
|||
)
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: openBlockedServicesModal,
|
||||
onTap: openBlockedServices,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.block),
|
||||
|
@ -341,20 +327,24 @@ class _FiltersState extends State<Filters> {
|
|||
}
|
||||
}
|
||||
|
||||
if (width > 1200) {
|
||||
return FiltersTripleColumn(
|
||||
onRemoveCustomRule: openRemoveCustomRuleModal,
|
||||
onOpenDetailsModal: openListDetails,
|
||||
actions: actions(),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return FiltersTabsView(
|
||||
appConfigProvider: appConfigProvider,
|
||||
actions: actions(),
|
||||
onRemoveCustomRule: openRemoveCustomRuleModal,
|
||||
onOpenDetailsModal: openListDetails,
|
||||
);
|
||||
}
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
if (constraints.maxWidth > 900) {
|
||||
return FiltersTripleColumn(
|
||||
onRemoveCustomRule: openRemoveCustomRuleModal,
|
||||
onOpenDetailsModal: openListDetails,
|
||||
actions: actions(),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return FiltersTabsView(
|
||||
appConfigProvider: appConfigProvider,
|
||||
actions: actions(),
|
||||
onRemoveCustomRule: openRemoveCustomRuleModal,
|
||||
onOpenDetailsModal: openListDetails,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -132,7 +132,7 @@ class _FiltersListState extends State<FiltersList> {
|
|||
TextButton.icon(
|
||||
onPressed: () async {
|
||||
final result = await filteringProvider.fetchFilters();
|
||||
if (result == false) {
|
||||
if (result == false && mounted) {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.errorLoadFilters,
|
||||
|
@ -172,7 +172,7 @@ class _FiltersListState extends State<FiltersList> {
|
|||
loadStatus: widget.loadStatus,
|
||||
onRefresh: () async {
|
||||
final result = await filteringProvider.fetchFilters();
|
||||
if (result == false) {
|
||||
if (result == false && mounted) {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.errorLoadFilters,
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|||
import 'package:adguard_home_manager/screens/filters/custom_rules_list.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/filters_list.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/desktop_mode.dart';
|
||||
import 'package:adguard_home_manager/providers/filtering_provider.dart';
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/models/filtering.dart';
|
||||
|
@ -17,12 +18,12 @@ class FiltersTabsView extends StatefulWidget {
|
|||
final void Function(Filter, String) onOpenDetailsModal;
|
||||
|
||||
const FiltersTabsView({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.appConfigProvider,
|
||||
required this.actions,
|
||||
required this.onOpenDetailsModal,
|
||||
required this.onRemoveCustomRule
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<FiltersTabsView> createState() => _FiltersTabsViewState();
|
||||
|
@ -47,6 +48,8 @@ class _FiltersTabsViewState extends State<FiltersTabsView> with TickerProviderSt
|
|||
Widget build(BuildContext context) {
|
||||
final filteringProvider = Provider.of<FilteringProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
return DefaultTabController(
|
||||
length: 3,
|
||||
child: NestedScrollView(
|
||||
|
@ -62,10 +65,12 @@ class _FiltersTabsViewState extends State<FiltersTabsView> with TickerProviderSt
|
|||
forceElevated: innerBoxIsScrolled,
|
||||
centerTitle: false,
|
||||
actions: widget.actions,
|
||||
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||
bottom: TabBar(
|
||||
controller: tabController,
|
||||
isScrollable: true,
|
||||
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
tabAlignment: TabAlignment.start,
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Row(
|
||||
|
@ -73,7 +78,7 @@ class _FiltersTabsViewState extends State<FiltersTabsView> with TickerProviderSt
|
|||
children: [
|
||||
const Icon(Icons.verified_user_rounded),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.whitelists,)
|
||||
Text(AppLocalizations.of(context)!.whitelists)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -7,12 +7,13 @@ import 'package:contextmenu/contextmenu.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/options_menu.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/add_button.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/list_options_menu.dart';
|
||||
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||
import 'package:adguard_home_manager/widgets/options_modal.dart';
|
||||
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/functions/desktop_mode.dart';
|
||||
import 'package:adguard_home_manager/models/menu_option.dart';
|
||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||
import 'package:adguard_home_manager/functions/copy_clipboard.dart';
|
||||
|
@ -27,16 +28,18 @@ class FiltersTripleColumn extends StatelessWidget {
|
|||
final List<Widget> actions;
|
||||
|
||||
const FiltersTripleColumn({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.onRemoveCustomRule,
|
||||
required this.onOpenDetailsModal,
|
||||
required this.actions,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filteringProvider = Provider.of<FilteringProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
Widget? generateSubtitle(String rule) {
|
||||
final allowRegex = RegExp(r'^@@.*$');
|
||||
|
@ -70,266 +73,17 @@ class FiltersTripleColumn extends StatelessWidget {
|
|||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Widget content() {
|
||||
switch (filteringProvider.loadStatus) {
|
||||
case LoadStatus.loading:
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.loadingFilters,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
case LoadStatus.loaded:
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.whitelists,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
),
|
||||
AddFiltersButton(
|
||||
type: 'whitelist',
|
||||
widget: (fn) => IconButton(
|
||||
onPressed: fn,
|
||||
icon: const Icon(Icons.add_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.addWhitelist,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: filteringProvider.filtering!.whitelistFilters.length,
|
||||
itemBuilder: (context, index) => ListOptionsMenu(
|
||||
list: filteringProvider.filtering!.whitelistFilters[index],
|
||||
listType: 'whitelist',
|
||||
child: CustomListTile(
|
||||
title: filteringProvider.filtering!.whitelistFilters[index].name,
|
||||
subtitle: "${intFormat(filteringProvider.filtering!.whitelistFilters[index].rulesCount, Platform.localeName)} ${AppLocalizations.of(context)!.enabledRules}",
|
||||
trailing: Icon(
|
||||
filteringProvider.filtering!.whitelistFilters[index].enabled == true
|
||||
? Icons.check_circle_rounded
|
||||
: Icons.cancel,
|
||||
color: filteringProvider.filtering!.whitelistFilters[index].enabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red
|
||||
),
|
||||
onTap: () => onOpenDetailsModal(filteringProvider.filtering!.whitelistFilters[index], 'whitelist'),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.blacklists,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
),
|
||||
AddFiltersButton(
|
||||
type: 'blacklist',
|
||||
widget: (fn) => IconButton(
|
||||
onPressed: fn,
|
||||
icon: const Icon(Icons.add_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.addBlacklist,
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: filteringProvider.filtering!.filters.length,
|
||||
itemBuilder: (context, index) => ListOptionsMenu(
|
||||
list: filteringProvider.filtering!.filters[index],
|
||||
listType: 'blacklist',
|
||||
child: CustomListTile(
|
||||
title: filteringProvider.filtering!.filters[index].name,
|
||||
subtitle: "${intFormat(filteringProvider.filtering!.filters[index].rulesCount, Platform.localeName)} ${AppLocalizations.of(context)!.enabledRules}",
|
||||
trailing: Icon(
|
||||
filteringProvider.filtering!.filters[index].enabled == true
|
||||
? Icons.check_circle_rounded
|
||||
: Icons.cancel,
|
||||
color: filteringProvider.filtering!.filters[index].enabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red
|
||||
),
|
||||
onTap: () => onOpenDetailsModal(filteringProvider.filtering!.filters[index], 'blacklist'),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.customRules,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
),
|
||||
AddFiltersButton(
|
||||
type: '',
|
||||
widget: (fn) => IconButton(
|
||||
onPressed: fn,
|
||||
icon: const Icon(Icons.add_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.addCustomRule,
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: filteringProvider.filtering!.userRules.length,
|
||||
itemBuilder: (context, index) => ContextMenuArea(
|
||||
builder: (context) => [
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
icon: Icons.copy_rounded,
|
||||
onTap: () {
|
||||
copyToClipboard(
|
||||
value: filteringProvider.filtering!.userRules[index],
|
||||
successMessage: AppLocalizations.of(context)!.copiedClipboard,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
),
|
||||
],
|
||||
child: CustomListTile(
|
||||
onLongPress: () => showDialog(
|
||||
context: context,
|
||||
builder: (context) => OptionsModal(
|
||||
options: [
|
||||
MenuOption(
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
icon: Icons.copy_rounded,
|
||||
action: () => copyToClipboard(
|
||||
value: filteringProvider.filtering!.userRules[index],
|
||||
successMessage: AppLocalizations.of(context)!.copiedClipboard,
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
title: filteringProvider.filtering!.userRules[index],
|
||||
subtitleWidget: generateSubtitle(filteringProvider.filtering!.userRules[index]),
|
||||
trailing: IconButton(
|
||||
onPressed: () => onRemoveCustomRule(filteringProvider.filtering!.userRules[index]),
|
||||
icon: const Icon(Icons.delete)
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
case LoadStatus.error:
|
||||
return SizedBox.expand(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
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)!.filtersNotLoaded,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||
title: Text(AppLocalizations.of(context)!.filters),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
final result = await filteringProvider.fetchFilters();
|
||||
if (result == false) {
|
||||
if (result == false && context.mounted) {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.errorLoadFilters,
|
||||
|
@ -343,7 +97,255 @@ class FiltersTripleColumn extends StatelessWidget {
|
|||
...actions
|
||||
],
|
||||
),
|
||||
body: content(),
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
switch (filteringProvider.loadStatus) {
|
||||
case LoadStatus.loading:
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.loadingFilters,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
case LoadStatus.loaded:
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.whitelists,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
),
|
||||
AddFiltersButton(
|
||||
type: 'whitelist',
|
||||
widget: (fn) => IconButton(
|
||||
onPressed: fn,
|
||||
icon: const Icon(Icons.add_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.addWhitelist,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: filteringProvider.filtering!.whitelistFilters.length,
|
||||
itemBuilder: (context, index) => ListOptionsMenu(
|
||||
list: filteringProvider.filtering!.whitelistFilters[index],
|
||||
listType: 'whitelist',
|
||||
child: CustomListTile(
|
||||
title: filteringProvider.filtering!.whitelistFilters[index].name,
|
||||
subtitle: "${intFormat(filteringProvider.filtering!.whitelistFilters[index].rulesCount, Platform.localeName)} ${AppLocalizations.of(context)!.enabledRules}",
|
||||
trailing: Icon(
|
||||
filteringProvider.filtering!.whitelistFilters[index].enabled == true
|
||||
? Icons.check_circle_rounded
|
||||
: Icons.cancel,
|
||||
color: filteringProvider.filtering!.whitelistFilters[index].enabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red
|
||||
),
|
||||
onTap: () => onOpenDetailsModal(filteringProvider.filtering!.whitelistFilters[index], 'whitelist'),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.blacklists,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
),
|
||||
AddFiltersButton(
|
||||
type: 'blacklist',
|
||||
widget: (fn) => IconButton(
|
||||
onPressed: fn,
|
||||
icon: const Icon(Icons.add_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.addBlacklist,
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: filteringProvider.filtering!.filters.length,
|
||||
itemBuilder: (context, index) => ListOptionsMenu(
|
||||
list: filteringProvider.filtering!.filters[index],
|
||||
listType: 'blacklist',
|
||||
child: CustomListTile(
|
||||
title: filteringProvider.filtering!.filters[index].name,
|
||||
subtitle: "${intFormat(filteringProvider.filtering!.filters[index].rulesCount, Platform.localeName)} ${AppLocalizations.of(context)!.enabledRules}",
|
||||
trailing: Icon(
|
||||
filteringProvider.filtering!.filters[index].enabled == true
|
||||
? Icons.check_circle_rounded
|
||||
: Icons.cancel,
|
||||
color: filteringProvider.filtering!.filters[index].enabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red
|
||||
),
|
||||
onTap: () => onOpenDetailsModal(filteringProvider.filtering!.filters[index], 'blacklist'),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.customRules,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
),
|
||||
AddFiltersButton(
|
||||
type: '',
|
||||
widget: (fn) => IconButton(
|
||||
onPressed: fn,
|
||||
icon: const Icon(Icons.add_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.addCustomRule,
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: filteringProvider.filtering!.userRules.length,
|
||||
itemBuilder: (context, index) => ContextMenuArea(
|
||||
builder: (context) => [
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
icon: Icons.copy_rounded,
|
||||
onTap: () {
|
||||
copyToClipboard(
|
||||
value: filteringProvider.filtering!.userRules[index],
|
||||
successMessage: AppLocalizations.of(context)!.copiedClipboard,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
),
|
||||
],
|
||||
child: OptionsMenu(
|
||||
options: (_) => [
|
||||
MenuOption(
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
icon: Icons.copy_rounded,
|
||||
action: () => copyToClipboard(
|
||||
value: filteringProvider.filtering!.userRules[index],
|
||||
successMessage: AppLocalizations.of(context)!.copiedClipboard,
|
||||
)
|
||||
)
|
||||
],
|
||||
child: CustomListTile(
|
||||
title: filteringProvider.filtering!.userRules[index],
|
||||
subtitleWidget: generateSubtitle(filteringProvider.filtering!.userRules[index]),
|
||||
trailing: IconButton(
|
||||
onPressed: () => onRemoveCustomRule(filteringProvider.filtering!.userRules[index]),
|
||||
icon: const Icon(Icons.delete),
|
||||
tooltip: AppLocalizations.of(context)!.delete,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
case LoadStatus.error:
|
||||
return SizedBox.expand(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
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)!.filtersNotLoaded,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,18 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:contextmenu/contextmenu.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||
import 'package:adguard_home_manager/widgets/options_modal.dart';
|
||||
import 'package:adguard_home_manager/widgets/options_menu.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/selection/selection_screen.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/open_url.dart';
|
||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||
import 'package:adguard_home_manager/models/filtering.dart';
|
||||
import 'package:adguard_home_manager/providers/filtering_provider.dart';
|
||||
|
@ -22,19 +26,21 @@ class ListOptionsMenu extends StatelessWidget {
|
|||
final String listType;
|
||||
|
||||
const ListOptionsMenu({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.list,
|
||||
required this.child,
|
||||
required this.listType,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filteringProvider = Provider.of<FilteringProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void enableDisable() async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(
|
||||
list.enabled == true
|
||||
? AppLocalizations.of(context)!.disablingList
|
||||
|
@ -50,7 +56,8 @@ class ListOptionsMenu extends StatelessWidget {
|
|||
);
|
||||
|
||||
processModal.close();
|
||||
|
||||
|
||||
if (!context.mounted) return;
|
||||
if (result == true) {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
|
@ -67,6 +74,32 @@ class ListOptionsMenu extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
void openSelectionMode() {
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
barrierColor: !(width > 900 || !(Platform.isAndroid | Platform.isIOS))
|
||||
?Colors.transparent
|
||||
: Colors.black54,
|
||||
transitionBuilder: (context, anim1, anim2, child) {
|
||||
return SlideTransition(
|
||||
position: Tween(
|
||||
begin: const Offset(0, 1),
|
||||
end: const Offset(0, 0)
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: anim1,
|
||||
curve: Curves.easeInOutCubicEmphasized
|
||||
)
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
pageBuilder: (context, animation, secondaryAnimation) => SelectionScreen(
|
||||
isModal: width > 900 || !(Platform.isAndroid | Platform.isIOS)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return ContextMenuArea(
|
||||
builder: (context) => [
|
||||
CustomListTile(
|
||||
|
@ -92,35 +125,61 @@ class ListOptionsMenu extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
),
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.openListUrl,
|
||||
icon: Icons.open_in_browser_rounded,
|
||||
onTap: () {
|
||||
Navigator.pop(context); // Closes the context menu
|
||||
openUrl(list.url);
|
||||
}
|
||||
),
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.selectionMode,
|
||||
icon: Icons.check_rounded,
|
||||
onTap: () {
|
||||
Navigator.pop(context); // Closes the context menu
|
||||
openSelectionMode();
|
||||
}
|
||||
),
|
||||
],
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onLongPress: () => showDialog(
|
||||
context: context,
|
||||
builder: (context) => OptionsModal(
|
||||
options: [
|
||||
MenuOption(
|
||||
title: list.enabled == true
|
||||
? AppLocalizations.of(context)!.disable
|
||||
: AppLocalizations.of(context)!.enable,
|
||||
icon: list.enabled == true
|
||||
? Icons.gpp_bad_rounded
|
||||
: Icons.verified_user_rounded,
|
||||
action: enableDisable
|
||||
),
|
||||
MenuOption(
|
||||
title: AppLocalizations.of(context)!.copyListUrl,
|
||||
icon: Icons.copy_rounded,
|
||||
action: () => copyToClipboard(
|
||||
value: list.url,
|
||||
successMessage: AppLocalizations.of(context)!.listUrlCopied
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
child: child
|
||||
child: OptionsMenu(
|
||||
options: (_) => [
|
||||
MenuOption(
|
||||
title: list.enabled == true
|
||||
? AppLocalizations.of(context)!.disable
|
||||
: AppLocalizations.of(context)!.enable,
|
||||
icon: list.enabled == true
|
||||
? Icons.gpp_bad_rounded
|
||||
: Icons.verified_user_rounded,
|
||||
action: () => enableDisable()
|
||||
),
|
||||
MenuOption(
|
||||
title: AppLocalizations.of(context)!.copyListUrl,
|
||||
icon: Icons.copy_rounded,
|
||||
action: () => copyToClipboard(
|
||||
value: list.url,
|
||||
successMessage: AppLocalizations.of(context)!.listUrlCopied
|
||||
)
|
||||
),
|
||||
MenuOption(
|
||||
title: AppLocalizations.of(context)!.openListUrl,
|
||||
icon: Icons.open_in_browser_rounded,
|
||||
action: () => openUrl(list.url)
|
||||
),
|
||||
MenuOption(
|
||||
title: AppLocalizations.of(context)!.selectionMode,
|
||||
icon: Icons.check_rounded,
|
||||
action: () => Future.delayed(
|
||||
const Duration(milliseconds: 0),
|
||||
() => openSelectionMode()
|
||||
)
|
||||
),
|
||||
],
|
||||
child: child
|
||||
)
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:segmented_button_slide/segmented_button_slide.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/open_url.dart';
|
||||
|
@ -6,13 +7,13 @@ import 'package:adguard_home_manager/constants/urls.dart';
|
|||
|
||||
class AddCustomRule extends StatefulWidget {
|
||||
final void Function(String) onConfirm;
|
||||
final bool dialog;
|
||||
final bool fullScreen;
|
||||
|
||||
const AddCustomRule({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.onConfirm,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
required this.fullScreen
|
||||
});
|
||||
|
||||
@override
|
||||
State<AddCustomRule> createState() => _AddCustomRuleState();
|
||||
|
@ -124,25 +125,26 @@ class _AddCustomRuleState extends State<AddCustomRule> {
|
|||
),
|
||||
),
|
||||
Container(height: 30),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: SegmentedButton(
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: BlockingPresets.block,
|
||||
label: Text(AppLocalizations.of(context)!.block)
|
||||
),
|
||||
ButtonSegment(
|
||||
value: BlockingPresets.unblock,
|
||||
label: Text(AppLocalizations.of(context)!.unblock)
|
||||
),
|
||||
ButtonSegment(
|
||||
value: BlockingPresets.custom,
|
||||
label: Text(AppLocalizations.of(context)!.custom)
|
||||
),
|
||||
],
|
||||
selected: <BlockingPresets>{preset},
|
||||
onSelectionChanged: (value) => setState(() => preset = value.first),
|
||||
SegmentedButtonSlide(
|
||||
entries: [
|
||||
SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.block),
|
||||
SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.unblock),
|
||||
SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.custom),
|
||||
],
|
||||
selectedEntry: preset.index,
|
||||
onChange: (v) => setState(() => preset = BlockingPresets.values[v]),
|
||||
colors: SegmentedButtonSlideColors(
|
||||
barColor: Theme.of(context).colorScheme.primary.withOpacity(0.2),
|
||||
backgroundSelectedColor: Theme.of(context).colorScheme.primary,
|
||||
foregroundSelectedColor: Theme.of(context).colorScheme.onPrimary,
|
||||
foregroundUnselectedColor: Theme.of(context).colorScheme.onSurface,
|
||||
hoverColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
textOverflow: TextOverflow.ellipsis,
|
||||
fontSize: 14,
|
||||
height: 40,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
),
|
||||
),
|
||||
Container(height: 20),
|
||||
|
@ -328,7 +330,34 @@ class _AddCustomRuleState extends State<AddCustomRule> {
|
|||
];
|
||||
}
|
||||
|
||||
if (widget.dialog == true) {
|
||||
if (widget.fullScreen == true) {
|
||||
return Dialog.fullscreen(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: CloseButton(onPressed: () => Navigator.pop(context)),
|
||||
title: Text(AppLocalizations.of(context)!.addCustomRule),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: checkValidValues() == true
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm(buildRule());
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.check)
|
||||
),
|
||||
const SizedBox(width: 10)
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
children: content(),
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
|
@ -383,27 +412,5 @@ class _AddCustomRuleState extends State<AddCustomRule> {
|
|||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.addCustomRule),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: checkValidValues() == true
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm(buildRule());
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.check)
|
||||
),
|
||||
const SizedBox(width: 10)
|
||||
],
|
||||
),
|
||||
body: ListView(
|
||||
children: content(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
317
lib/screens/filters/modals/blocked_services_screen.dart
Normal file
317
lib/screens/filters/modals/blocked_services_screen.dart
Normal file
|
@ -0,0 +1,317 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/blocked_services.dart';
|
||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/providers/filtering_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
|
||||
class BlockedServicesScreen extends StatefulWidget {
|
||||
final bool fullScreen;
|
||||
|
||||
const BlockedServicesScreen({
|
||||
super.key,
|
||||
required this.fullScreen
|
||||
});
|
||||
|
||||
@override
|
||||
State<BlockedServicesScreen> createState() => _BlockedServicesScreenStateWidget();
|
||||
}
|
||||
|
||||
class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreen> {
|
||||
List<String> values = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final filteringProvider = Provider.of<FilteringProvider>(context, listen: false);
|
||||
|
||||
if (filteringProvider.blockedServicesLoadStatus != LoadStatus.loaded) {
|
||||
filteringProvider.loadBlockedServices(showLoading: true);
|
||||
}
|
||||
|
||||
values = filteringProvider.filtering!.blockedServices;
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filteringProvider = Provider.of<FilteringProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
void updateValues(bool value, BlockedService item) {
|
||||
if (value == true) {
|
||||
setState(() {
|
||||
values = values.where((v) => v != item.id).toList();
|
||||
});
|
||||
}
|
||||
else {
|
||||
setState(() {
|
||||
values.add(item.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void updateBlockedServices() async {
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.updating);
|
||||
|
||||
final result = await filteringProvider.updateBlockedServices(values);
|
||||
|
||||
processModal.close();
|
||||
|
||||
if (result == true) {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.blockedServicesUpdated,
|
||||
color: Colors.green
|
||||
);
|
||||
}
|
||||
else {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.blockedServicesNotUpdated,
|
||||
color: Colors.red
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (widget.fullScreen == true) {
|
||||
return Dialog.fullscreen(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: CloseButton(onPressed: () => Navigator.pop(context)),
|
||||
title: Text(AppLocalizations.of(context)!.blockedServices),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: updateBlockedServices,
|
||||
icon: const Icon(
|
||||
Icons.save_rounded
|
||||
),
|
||||
tooltip: AppLocalizations.of(context)!.save,
|
||||
),
|
||||
const SizedBox(width: 10)
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
final result = await filteringProvider.loadBlockedServices();
|
||||
if (result == false) {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.blockedServicesListNotLoaded,
|
||||
color: Colors.red
|
||||
);
|
||||
}
|
||||
},
|
||||
child: _Content(
|
||||
values: values,
|
||||
updateValues: updateValues,
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 400
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.clear_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.close,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.blockedServices,
|
||||
style: const TextStyle(
|
||||
fontSize: 22
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
onPressed: updateBlockedServices,
|
||||
icon: const Icon(
|
||||
Icons.save_rounded
|
||||
),
|
||||
tooltip: AppLocalizations.of(context)!.save,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _Content(
|
||||
values: values,
|
||||
updateValues: updateValues,
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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({
|
||||
required BuildContext context,
|
||||
required double width,
|
||||
}) {
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
barrierColor: !(width > 700 || !(Platform.isAndroid || Platform.isIOS))
|
||||
?Colors.transparent
|
||||
: Colors.black54,
|
||||
transitionBuilder: (context, anim1, anim2, child) {
|
||||
return SlideTransition(
|
||||
position: Tween(
|
||||
begin: const Offset(0, 1),
|
||||
end: const Offset(0, 0)
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: anim1,
|
||||
curve: Curves.easeInOutCubicEmphasized
|
||||
)
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
pageBuilder: (context, animation, secondaryAnimation) => BlockedServicesScreen(
|
||||
fullScreen: !(width > 700 || !(Platform.isAndroid || Platform.isIOS)),
|
||||
),
|
||||
);
|
||||
}
|
|
@ -5,9 +5,9 @@ class DeleteListModal extends StatelessWidget {
|
|||
final void Function() onConfirm;
|
||||
|
||||
const DeleteListModal({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.onConfirm
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
256
lib/screens/filters/modals/update_interval_lists_modal.dart
Normal file
256
lib/screens/filters/modals/update_interval_lists_modal.dart
Normal file
|
@ -0,0 +1,256 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/option_box.dart';
|
||||
|
||||
|
||||
class UpdateIntervalListsModal extends StatefulWidget {
|
||||
final int interval;
|
||||
final void Function(int) onChange;
|
||||
final bool dialog;
|
||||
|
||||
const UpdateIntervalListsModal({
|
||||
super.key,
|
||||
required this.interval,
|
||||
required this.onChange,
|
||||
required this.dialog
|
||||
});
|
||||
|
||||
@override
|
||||
State<UpdateIntervalListsModal> createState() => _UpdateIntervalListsModalState();
|
||||
}
|
||||
|
||||
class _UpdateIntervalListsModalState extends State<UpdateIntervalListsModal> {
|
||||
int? selectedOption;
|
||||
|
||||
void _updateRadioValue(int value) {
|
||||
setState(() {
|
||||
selectedOption = value;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
selectedOption = widget.interval;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
||||
|
||||
if (widget.dialog == true) {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: _Content(
|
||||
selectedOption: selectedOption,
|
||||
onUpdateValue: _updateRadioValue,
|
||||
onConfirm: () => widget.onChange(selectedOption!),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Padding(
|
||||
padding: mediaQueryData.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(
|
||||
selectedOption: selectedOption,
|
||||
onUpdateValue: _updateRadioValue,
|
||||
onConfirm: () => widget.onChange(selectedOption!),
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Content extends StatelessWidget {
|
||||
final int? selectedOption;
|
||||
final void Function(int) onUpdateValue;
|
||||
final void Function() onConfirm;
|
||||
|
||||
const _Content({
|
||||
required this.selectedOption,
|
||||
required this.onUpdateValue,
|
||||
required this.onConfirm,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: Icon(
|
||||
Icons.update_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 16
|
||||
),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.updateFrequency,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: OptionBox(
|
||||
optionsValue: selectedOption,
|
||||
itemValue: 0,
|
||||
onTap: (v) => onUpdateValue(v as int),
|
||||
label: AppLocalizations.of(context)!.never,
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
child: OptionBox(
|
||||
optionsValue: selectedOption,
|
||||
itemValue: 1,
|
||||
onTap: (v) => onUpdateValue(v as int),
|
||||
label: AppLocalizations.of(context)!.hour1,
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: OptionBox(
|
||||
optionsValue: selectedOption,
|
||||
itemValue: 12,
|
||||
onTap: (v) => onUpdateValue(v as int),
|
||||
label: AppLocalizations.of(context)!.hours12,
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
child: OptionBox(
|
||||
optionsValue: selectedOption,
|
||||
itemValue: 24,
|
||||
onTap: (v) => onUpdateValue(v as int),
|
||||
label: AppLocalizations.of(context)!.hours24,
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: OptionBox(
|
||||
optionsValue: selectedOption,
|
||||
itemValue: 72,
|
||||
onTap: (v) => onUpdateValue(v as int),
|
||||
label: AppLocalizations.of(context)!.days3,
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
child: OptionBox(
|
||||
optionsValue: selectedOption,
|
||||
itemValue: 168,
|
||||
onTap: (v) => onUpdateValue(v as int),
|
||||
label: AppLocalizations.of(context)!.days7,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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: selectedOption != null
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
onConfirm();
|
||||
}
|
||||
: null,
|
||||
style: ButtonStyle(
|
||||
overlayColor: MaterialStateProperty.all(
|
||||
Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
||||
),
|
||||
foregroundColor: MaterialStateProperty.all(
|
||||
selectedOption != null
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.grey,
|
||||
),
|
||||
),
|
||||
child: Text(AppLocalizations.of(context)!.confirm),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (Platform.isIOS) const SizedBox(height: 16)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
150
lib/screens/filters/selection/delete_selection_modal.dart
Normal file
150
lib/screens/filters/selection/delete_selection_modal.dart
Normal file
|
@ -0,0 +1,150 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/filtering.dart';
|
||||
|
||||
class DeleteSelectionModal extends StatefulWidget {
|
||||
final List<Filter> selectedWhitelists;
|
||||
final List<Filter> selectedBlacklists;
|
||||
final void Function() onDelete;
|
||||
|
||||
const DeleteSelectionModal({
|
||||
Key? key,
|
||||
required this.selectedBlacklists,
|
||||
required this.selectedWhitelists,
|
||||
required this.onDelete,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<DeleteSelectionModal> createState() => _DeleteSelectionModalState();
|
||||
}
|
||||
|
||||
class _DeleteSelectionModalState extends State<DeleteSelectionModal> {
|
||||
bool _whitelistExpanded = true;
|
||||
bool _blacklistExpanded = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.shield_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.deleteSelectedLists,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
content: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
if (widget.selectedWhitelists.isNotEmpty) ExpansionPanelList(
|
||||
expandedHeaderPadding: const EdgeInsets.all(0),
|
||||
elevation: 0,
|
||||
expansionCallback: (_, isExpanded) => setState(() => _whitelistExpanded = isExpanded),
|
||||
animationDuration: const Duration(milliseconds: 250),
|
||||
children: [
|
||||
ExpansionPanel(
|
||||
backgroundColor: Colors.transparent,
|
||||
headerBuilder: (context, isExpanded) => Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.whitelists,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ListView.builder(
|
||||
primary: false,
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.selectedWhitelists.length,
|
||||
itemBuilder: (context, index) => Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Text("• ${widget.selectedWhitelists[index].name}"),
|
||||
),
|
||||
),
|
||||
isExpanded: _whitelistExpanded
|
||||
),
|
||||
],
|
||||
),
|
||||
if (widget.selectedWhitelists.isNotEmpty && widget.selectedBlacklists.isNotEmpty) const Padding(padding: EdgeInsets.all(8)),
|
||||
if (widget.selectedBlacklists.isNotEmpty) ExpansionPanelList(
|
||||
expandedHeaderPadding: const EdgeInsets.all(0),
|
||||
elevation: 0,
|
||||
expansionCallback: (_, isExpanded) => setState(() => _blacklistExpanded = isExpanded),
|
||||
animationDuration: const Duration(milliseconds: 250),
|
||||
children: [
|
||||
ExpansionPanel(
|
||||
backgroundColor: Colors.transparent,
|
||||
headerBuilder: (context, isExpanded) => Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.blacklists,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ListView.builder(
|
||||
primary: false,
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.selectedBlacklists.length,
|
||||
itemBuilder: (context, index) => Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Text("• ${widget.selectedBlacklists[index].name}"),
|
||||
),
|
||||
),
|
||||
isExpanded: _blacklistExpanded
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel)
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
TextButton(
|
||||
onPressed: widget.onDelete,
|
||||
child: Text(AppLocalizations.of(context)!.confirm)
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/filtering.dart';
|
||||
|
||||
class EnableDisableSelectionModal extends StatefulWidget {
|
||||
final List<Filter> selectedWhitelists;
|
||||
final List<Filter> selectedBlacklists;
|
||||
final void Function() onDelete;
|
||||
|
||||
const EnableDisableSelectionModal({
|
||||
Key? key,
|
||||
required this.selectedBlacklists,
|
||||
required this.selectedWhitelists,
|
||||
required this.onDelete,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<EnableDisableSelectionModal> createState() => _EnableDisableSelectionModalState();
|
||||
}
|
||||
|
||||
class _EnableDisableSelectionModalState extends State<EnableDisableSelectionModal> {
|
||||
bool _whitelistExpanded = true;
|
||||
bool _blacklistExpanded = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.remove_moderator_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.enableDisableSelected,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
content: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
if (widget.selectedWhitelists.isNotEmpty) ExpansionPanelList(
|
||||
expandedHeaderPadding: const EdgeInsets.all(0),
|
||||
elevation: 0,
|
||||
expansionCallback: (_, isExpanded) => setState(() => _whitelistExpanded = isExpanded),
|
||||
animationDuration: const Duration(milliseconds: 250),
|
||||
children: [
|
||||
ExpansionPanel(
|
||||
backgroundColor: Colors.transparent,
|
||||
headerBuilder: (context, isExpanded) => Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.whitelists,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ListView.builder(
|
||||
primary: false,
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.selectedWhitelists.length,
|
||||
itemBuilder: (context, index) => Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
"• ${widget.selectedWhitelists[index].name}",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
widget.selectedWhitelists[index].enabled == true
|
||||
? AppLocalizations.of(context)!.disable
|
||||
: AppLocalizations.of(context)!.enable,
|
||||
style: TextStyle(
|
||||
color: widget.selectedWhitelists[index].enabled == true
|
||||
? Colors.red
|
||||
: Colors.green,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
isExpanded: _whitelistExpanded
|
||||
),
|
||||
],
|
||||
),
|
||||
if (widget.selectedWhitelists.isNotEmpty && widget.selectedBlacklists.isNotEmpty) const Padding(padding: EdgeInsets.all(8)),
|
||||
if (widget.selectedBlacklists.isNotEmpty) ExpansionPanelList(
|
||||
expandedHeaderPadding: const EdgeInsets.all(0),
|
||||
elevation: 0,
|
||||
expansionCallback: (_, isExpanded) => setState(() => _blacklistExpanded = isExpanded),
|
||||
animationDuration: const Duration(milliseconds: 250),
|
||||
children: [
|
||||
ExpansionPanel(
|
||||
backgroundColor: Colors.transparent,
|
||||
headerBuilder: (context, isExpanded) => Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.blacklists,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ListView.builder(
|
||||
primary: false,
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.selectedBlacklists.length,
|
||||
itemBuilder: (context, index) => Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
"• ${widget.selectedBlacklists[index].name}",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
widget.selectedBlacklists[index].enabled == true
|
||||
? AppLocalizations.of(context)!.disable
|
||||
: AppLocalizations.of(context)!.enable,
|
||||
style: TextStyle(
|
||||
color: widget.selectedBlacklists[index].enabled == true
|
||||
? Colors.red
|
||||
: Colors.green,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
isExpanded: _blacklistExpanded
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel)
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
TextButton(
|
||||
onPressed: widget.onDelete,
|
||||
child: Text(AppLocalizations.of(context)!.confirm)
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
312
lib/screens/filters/selection/selection_lists.dart
Normal file
312
lib/screens/filters/selection/selection_lists.dart
Normal file
|
@ -0,0 +1,312 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/filtering.dart';
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
|
||||
class SelectionList extends StatelessWidget {
|
||||
final List<Filter> lists;
|
||||
final List<Filter> selectedLists;
|
||||
final void Function(Filter) onSelect;
|
||||
final void Function() selectAll;
|
||||
final void Function() unselectAll;
|
||||
|
||||
const SelectionList({
|
||||
Key? key,
|
||||
required this.lists,
|
||||
required this.selectedLists,
|
||||
required this.onSelect,
|
||||
required this.selectAll,
|
||||
required this.unselectAll,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
itemCount: lists.length+1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.all(16),
|
||||
child: CheckboxListTile(
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.selectAll,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
value: lists.length == selectedLists.length,
|
||||
onChanged: (value) {
|
||||
if (value == true) {
|
||||
selectAll();
|
||||
}
|
||||
else {
|
||||
unselectAll();
|
||||
}
|
||||
},
|
||||
checkboxShape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4)
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12)
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return _CheckboxTile(
|
||||
list: lists[index-1],
|
||||
onSelect: onSelect,
|
||||
isSelected: selectedLists.contains(lists[index-1]),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionSliverList extends StatelessWidget {
|
||||
final List<Filter> lists;
|
||||
final List<Filter> selectedLists;
|
||||
final void Function(Filter) onSelect;
|
||||
final void Function() selectAll;
|
||||
final void Function() unselectAll;
|
||||
|
||||
const SelectionSliverList({
|
||||
super.key,
|
||||
required this.lists,
|
||||
required this.selectedLists,
|
||||
required this.onSelect,
|
||||
required this.selectAll,
|
||||
required this.unselectAll,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverOverlapInjector(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
if (lists.isNotEmpty) SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (index == 0) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.all(16),
|
||||
child: CheckboxListTile(
|
||||
title: Text(AppLocalizations.of(context)!.selectAll),
|
||||
value: lists.length == selectedLists.length,
|
||||
onChanged: (value) {
|
||||
if (value == true) {
|
||||
selectAll();
|
||||
}
|
||||
else {
|
||||
unselectAll();
|
||||
}
|
||||
},
|
||||
checkboxShape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4)
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12)
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return _Tile(
|
||||
list: lists[index-1],
|
||||
onSelect: onSelect,
|
||||
isSelected: selectedLists.contains(lists[index-1]),
|
||||
);
|
||||
},
|
||||
childCount: lists.length+1
|
||||
),
|
||||
),
|
||||
if (lists.isEmpty) SliverFillRemaining(
|
||||
child: Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.noItems,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Tile extends StatelessWidget {
|
||||
final Filter list;
|
||||
final void Function(Filter) onSelect;
|
||||
final bool isSelected;
|
||||
|
||||
const _Tile({
|
||||
required this.list,
|
||||
required this.onSelect,
|
||||
required this.isSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
return ListTile(
|
||||
title: Text(
|
||||
list.name,
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
list.url,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
list.enabled == true
|
||||
? Icons.check_rounded
|
||||
: Icons.close_rounded,
|
||||
size: 16,
|
||||
color: list.enabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
list.enabled == true
|
||||
? AppLocalizations.of(context)!.enabled
|
||||
: AppLocalizations.of(context)!.disabled,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: list.enabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
isThreeLine: true,
|
||||
tileColor: isSelected == true
|
||||
? Theme.of(context).colorScheme.primaryContainer
|
||||
: null,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 16
|
||||
),
|
||||
selected: isSelected,
|
||||
selectedTileColor: Theme.of(context).colorScheme.primaryContainer.withOpacity(0.5),
|
||||
selectedColor: Theme.of(context).colorScheme.onSurface,
|
||||
onTap: () => onSelect(list),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CheckboxTile extends StatelessWidget {
|
||||
final Filter list;
|
||||
final void Function(Filter) onSelect;
|
||||
final bool isSelected;
|
||||
|
||||
const _CheckboxTile({
|
||||
Key? key,
|
||||
required this.list,
|
||||
required this.onSelect,
|
||||
required this.isSelected,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
return ListTile(
|
||||
leading: Checkbox(
|
||||
value: isSelected,
|
||||
onChanged: (_) => onSelect(list),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4)
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
list.name,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
list.url,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
list.enabled == true
|
||||
? Icons.check_rounded
|
||||
: Icons.close_rounded,
|
||||
size: 16,
|
||||
color: list.enabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
list.enabled == true
|
||||
? AppLocalizations.of(context)!.enabled
|
||||
: AppLocalizations.of(context)!.disabled,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: list.enabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
isThreeLine: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 16
|
||||
),
|
||||
onTap: () => onSelect(list),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
102
lib/screens/filters/selection/selection_result_modal.dart
Normal file
102
lib/screens/filters/selection/selection_result_modal.dart
Normal file
|
@ -0,0 +1,102 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/filtering.dart';
|
||||
|
||||
enum SelectionResultMode { delete, enableDisable }
|
||||
|
||||
class SelectionResultModal extends StatelessWidget {
|
||||
final List<ProcessedList> results;
|
||||
final void Function() onClose;
|
||||
final SelectionResultMode mode;
|
||||
|
||||
const SelectionResultModal({
|
||||
super.key,
|
||||
required this.results,
|
||||
required this.onClose,
|
||||
required this.mode,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final failedItems = results.where((r) => r.successful == false).toList();
|
||||
|
||||
return AlertDialog(
|
||||
title: Column(
|
||||
children: [
|
||||
Icon(
|
||||
mode == SelectionResultMode.delete
|
||||
? Icons.delete_rounded
|
||||
: Icons.remove_moderator_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
mode == SelectionResultMode.delete
|
||||
? AppLocalizations.of(context)!.deletionResult
|
||||
: AppLocalizations.of(context)!.enableDisableResult,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
content: failedItems.isEmpty
|
||||
? Text(
|
||||
mode == SelectionResultMode.delete
|
||||
? AppLocalizations.of(context)!.allSelectedListsDeletedSuccessfully
|
||||
: AppLocalizations.of(context)!.selectedListsEnabledDisabledSuccessfully,
|
||||
)
|
||||
: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16, top: 8),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.failedElements,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
ListView.builder(
|
||||
primary: false,
|
||||
shrinkWrap: true,
|
||||
itemCount: failedItems.length,
|
||||
itemBuilder: (context, index) => Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Text("• ${failedItems[index].list.name}"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
onClose();
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!.close)
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
376
lib/screens/filters/selection/selection_screen.dart
Normal file
376
lib/screens/filters/selection/selection_screen.dart
Normal file
|
@ -0,0 +1,376 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/filters/selection/enable_disable_selection_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/selection/delete_selection_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/selection/selection_result_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/selection/selection_lists.dart';
|
||||
|
||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||
import 'package:adguard_home_manager/models/filtering.dart';
|
||||
import 'package:adguard_home_manager/providers/filtering_provider.dart';
|
||||
|
||||
enum ListType { blacklist, whitelist }
|
||||
|
||||
class SelectionScreen extends StatefulWidget {
|
||||
final bool isModal;
|
||||
|
||||
const SelectionScreen({
|
||||
super.key,
|
||||
required this.isModal
|
||||
});
|
||||
|
||||
@override
|
||||
State<SelectionScreen> createState() => _SelectionScreenState();
|
||||
}
|
||||
|
||||
class _SelectionScreenState extends State<SelectionScreen> with TickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
|
||||
List<Filter> _selectedWhitelists = [];
|
||||
List<Filter> _selectedBlacklists = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(
|
||||
initialIndex: 0,
|
||||
length: 2,
|
||||
vsync: this,
|
||||
);
|
||||
}
|
||||
|
||||
void handleSelect(Filter list, ListType type) {
|
||||
if (type == ListType.blacklist) {
|
||||
final isContained = _selectedBlacklists.contains(list);
|
||||
if (isContained) {
|
||||
setState(() => _selectedBlacklists = _selectedBlacklists.where((l) => l != list).toList());
|
||||
}
|
||||
else {
|
||||
setState(() => _selectedBlacklists.add(list));
|
||||
}
|
||||
}
|
||||
else if (type == ListType.whitelist) {
|
||||
final isContained = _selectedWhitelists.contains(list);
|
||||
if (isContained) {
|
||||
setState(() => _selectedWhitelists = _selectedWhitelists.where((l) => l != list).toList());
|
||||
}
|
||||
else {
|
||||
setState(() => _selectedWhitelists.add(list));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filteringProvider = Provider.of<FilteringProvider>(context);
|
||||
|
||||
final somethingSelected = _selectedBlacklists.isNotEmpty || _selectedWhitelists.isNotEmpty;
|
||||
|
||||
void enableDisableSelected() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => EnableDisableSelectionModal(
|
||||
selectedWhitelists: _selectedWhitelists,
|
||||
selectedBlacklists: _selectedBlacklists,
|
||||
onDelete: () async {
|
||||
Navigator.pop(context);
|
||||
final processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.processingLists);
|
||||
final result = await filteringProvider.enableDisableMultipleLists(
|
||||
blacklists: _selectedBlacklists,
|
||||
whitelists: _selectedWhitelists
|
||||
);
|
||||
if (!mounted) return;
|
||||
processModal.close();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => SelectionResultModal(
|
||||
mode: SelectionResultMode.enableDisable,
|
||||
results: result,
|
||||
onClose: () => Navigator.pop(context),
|
||||
),
|
||||
barrierDismissible: false
|
||||
);
|
||||
},
|
||||
),
|
||||
barrierDismissible: false
|
||||
);
|
||||
}
|
||||
|
||||
void deleteSelected() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => DeleteSelectionModal(
|
||||
selectedWhitelists: _selectedWhitelists,
|
||||
selectedBlacklists: _selectedBlacklists,
|
||||
onDelete: () async {
|
||||
Navigator.pop(context);
|
||||
final processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.deletingLists);
|
||||
final result = await filteringProvider.deleteMultipleLists(
|
||||
blacklists: _selectedBlacklists,
|
||||
whitelists: _selectedWhitelists
|
||||
);
|
||||
if (!mounted) return;
|
||||
processModal.close();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => SelectionResultModal(
|
||||
mode: SelectionResultMode.delete,
|
||||
results: result,
|
||||
onClose: () => Navigator.pop(context),
|
||||
),
|
||||
barrierDismissible: false
|
||||
);
|
||||
},
|
||||
),
|
||||
barrierDismissible: false
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.isModal == true) {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.clear_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.close,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.selectionMode,
|
||||
style: const TextStyle(
|
||||
fontSize: 22
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: somethingSelected == true
|
||||
? () => enableDisableSelected()
|
||||
: null,
|
||||
icon: const Icon(Icons.remove_moderator_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.enableDisableSelected,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
onPressed: somethingSelected == true
|
||||
? () => deleteSelected()
|
||||
: null,
|
||||
icon: const Icon(Icons.delete_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.deleteSelectedLists,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: DefaultTabController(
|
||||
length: 2,
|
||||
child: Column(
|
||||
children: [
|
||||
TabBar(
|
||||
controller: _tabController,
|
||||
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
tabs: [
|
||||
_Tab(
|
||||
icon: Icons.verified_user_rounded,
|
||||
text: AppLocalizations.of(context)!.whitelists,
|
||||
quantity: _selectedWhitelists.length
|
||||
),
|
||||
_Tab(
|
||||
icon: Icons.gpp_bad_rounded,
|
||||
text: AppLocalizations.of(context)!.blacklists,
|
||||
quantity: _selectedBlacklists.length
|
||||
),
|
||||
]
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
SelectionList(
|
||||
lists: filteringProvider.filtering!.whitelistFilters,
|
||||
selectedLists: _selectedWhitelists,
|
||||
onSelect: (list) => handleSelect(list, ListType.whitelist),
|
||||
selectAll: () => setState(() => _selectedWhitelists = filteringProvider.filtering!.whitelistFilters),
|
||||
unselectAll: () => setState(() => _selectedWhitelists = [])
|
||||
),
|
||||
SelectionList(
|
||||
lists: filteringProvider.filtering!.filters,
|
||||
selectedLists: _selectedBlacklists,
|
||||
onSelect: (list) => handleSelect(list, ListType.blacklist),
|
||||
selectAll: () => setState(() => _selectedBlacklists = filteringProvider.filtering!.filters),
|
||||
unselectAll: () => setState(() => _selectedBlacklists = [])
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Dialog.fullscreen(
|
||||
child: Stack(
|
||||
children: [
|
||||
DefaultTabController(
|
||||
length: 2,
|
||||
child: NestedScrollView(
|
||||
headerSliverBuilder: ((context, innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverOverlapAbsorber(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: SliverAppBar(
|
||||
leading: CloseButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
title: Text(AppLocalizations.of(context)!.selectionMode),
|
||||
pinned: true,
|
||||
floating: true,
|
||||
forceElevated: innerBoxIsScrolled,
|
||||
centerTitle: false,
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
tabs: [
|
||||
_Tab(
|
||||
icon: Icons.verified_user_rounded,
|
||||
text: AppLocalizations.of(context)!.whitelists,
|
||||
quantity: _selectedWhitelists.length
|
||||
),
|
||||
_Tab(
|
||||
icon: Icons.gpp_bad_rounded,
|
||||
text: AppLocalizations.of(context)!.blacklists,
|
||||
quantity: _selectedBlacklists.length
|
||||
),
|
||||
]
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: somethingSelected == true
|
||||
? () => enableDisableSelected()
|
||||
: null,
|
||||
icon: const Icon(Icons.remove_moderator_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.enableDisableSelected,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: somethingSelected == true
|
||||
? () => deleteSelected()
|
||||
: null,
|
||||
icon: const Icon(Icons.delete_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.deleteSelectedLists,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
)
|
||||
];
|
||||
}),
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
SelectionSliverList(
|
||||
lists: filteringProvider.filtering!.whitelistFilters,
|
||||
selectedLists: _selectedWhitelists,
|
||||
onSelect: (list) => handleSelect(list, ListType.whitelist),
|
||||
selectAll: () => setState(() => _selectedWhitelists = filteringProvider.filtering!.whitelistFilters),
|
||||
unselectAll: () => setState(() => _selectedWhitelists = []),
|
||||
),
|
||||
SelectionSliverList(
|
||||
lists: filteringProvider.filtering!.filters,
|
||||
selectedLists: _selectedBlacklists,
|
||||
onSelect: (list) => handleSelect(list, ListType.blacklist),
|
||||
selectAll: () => setState(() => _selectedBlacklists = filteringProvider.filtering!.filters),
|
||||
unselectAll: () => setState(() => _selectedBlacklists = []),
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Tab extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String text;
|
||||
final int quantity;
|
||||
|
||||
const _Tab({
|
||||
required this.icon,
|
||||
required this.text,
|
||||
required this.quantity
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
text,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
height: 22,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 2
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
color: Theme.of(context).colorScheme.primaryContainer
|
||||
),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 18
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
quantity.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,304 +0,0 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/option_box.dart';
|
||||
|
||||
|
||||
class UpdateIntervalListsModal extends StatefulWidget {
|
||||
final int interval;
|
||||
final void Function(int) onChange;
|
||||
final bool dialog;
|
||||
|
||||
const UpdateIntervalListsModal({
|
||||
Key? key,
|
||||
required this.interval,
|
||||
required this.onChange,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<UpdateIntervalListsModal> createState() => _UpdateIntervalListsModalState();
|
||||
}
|
||||
|
||||
class _UpdateIntervalListsModalState extends State<UpdateIntervalListsModal> {
|
||||
int? selectedOption;
|
||||
|
||||
void _updateRadioValue(value) {
|
||||
setState(() {
|
||||
selectedOption = value;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
selectedOption = widget.interval;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
||||
|
||||
Widget content() {
|
||||
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.update_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 16
|
||||
),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.updateFrequency,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: OptionBox(
|
||||
optionsValue: selectedOption,
|
||||
itemValue: 0,
|
||||
onTap: _updateRadioValue,
|
||||
child: Center(
|
||||
child: AnimatedDefaultTextStyle(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: selectedOption == 0
|
||||
? Theme.of(context).colorScheme.onInverseSurface
|
||||
: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
child: Text(AppLocalizations.of(context)!.never),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
child: OptionBox(
|
||||
optionsValue: selectedOption,
|
||||
itemValue: 1,
|
||||
onTap: _updateRadioValue,
|
||||
child: Center(
|
||||
child: AnimatedDefaultTextStyle(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: selectedOption == 1
|
||||
? Theme.of(context).colorScheme.onInverseSurface
|
||||
: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
child: Text(AppLocalizations.of(context)!.hour1),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: OptionBox(
|
||||
optionsValue: selectedOption,
|
||||
itemValue: 12,
|
||||
onTap: _updateRadioValue,
|
||||
child: Center(
|
||||
child: AnimatedDefaultTextStyle(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: selectedOption == 12
|
||||
? Theme.of(context).colorScheme.onInverseSurface
|
||||
: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
child: Text(AppLocalizations.of(context)!.hours12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
child: OptionBox(
|
||||
optionsValue: selectedOption,
|
||||
itemValue: 24,
|
||||
onTap: _updateRadioValue,
|
||||
child: Center(
|
||||
child: AnimatedDefaultTextStyle(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: selectedOption == 24
|
||||
? Theme.of(context).colorScheme.onInverseSurface
|
||||
: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
child: Text(AppLocalizations.of(context)!.hours24),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: OptionBox(
|
||||
optionsValue: selectedOption,
|
||||
itemValue: 72,
|
||||
onTap: _updateRadioValue,
|
||||
child: Center(
|
||||
child: AnimatedDefaultTextStyle(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: selectedOption == 72
|
||||
? Theme.of(context).colorScheme.onInverseSurface
|
||||
: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
child: Text(AppLocalizations.of(context)!.days3),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
child: OptionBox(
|
||||
optionsValue: selectedOption,
|
||||
itemValue: 168,
|
||||
onTap: _updateRadioValue,
|
||||
child: Center(
|
||||
child: AnimatedDefaultTextStyle(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: selectedOption == 168
|
||||
? Theme.of(context).colorScheme.onInverseSurface
|
||||
: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
child: Text(AppLocalizations.of(context)!.days7),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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: selectedOption != null
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onChange(selectedOption!);
|
||||
}
|
||||
: null,
|
||||
style: ButtonStyle(
|
||||
overlayColor: MaterialStateProperty.all(
|
||||
Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
||||
),
|
||||
foregroundColor: MaterialStateProperty.all(
|
||||
selectedOption != null
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.grey,
|
||||
),
|
||||
),
|
||||
child: Text(AppLocalizations.of(context)!.confirm),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (Platform.isIOS) const SizedBox(height: 16)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.dialog == true) {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: content()
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Padding(
|
||||
padding: mediaQueryData.viewInsets,
|
||||
child: Container(
|
||||
height: Platform.isIOS ? 406 : 390,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).dialogBackgroundColor,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
topRight: Radius.circular(28)
|
||||
),
|
||||
),
|
||||
child: content()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/servers/servers.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/desktop_mode.dart';
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||
import 'package:adguard_home_manager/functions/open_url.dart';
|
||||
|
@ -15,9 +18,9 @@ class HomeAppBar extends StatelessWidget {
|
|||
final bool innerBoxScrolled;
|
||||
|
||||
const HomeAppBar({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.innerBoxScrolled
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -25,6 +28,8 @@ class HomeAppBar extends StatelessWidget {
|
|||
final statusProvider = Provider.of<StatusProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
final Server? server = serversProvider.selectedServer;
|
||||
|
||||
void navigateServers() {
|
||||
|
@ -40,6 +45,7 @@ class HomeAppBar extends StatelessWidget {
|
|||
floating: true,
|
||||
centerTitle: false,
|
||||
forceElevated: innerBoxScrolled,
|
||||
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||
leading: Stack(
|
||||
children: [
|
||||
Center(
|
||||
|
@ -114,6 +120,14 @@ class HomeAppBar extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
actions: [
|
||||
if (!(Platform.isAndroid || Platform.isIOS)) ...[
|
||||
IconButton(
|
||||
onPressed: () => statusProvider.getServerStatus(),
|
||||
icon: const Icon(Icons.refresh_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.refresh,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
PopupMenuButton(
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
|
|
|
@ -1,39 +1,51 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/line_chart.dart';
|
||||
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
|
||||
class HomeChart extends StatelessWidget {
|
||||
class HomeChart extends StatefulWidget {
|
||||
final List<int> data;
|
||||
final String label;
|
||||
final String primaryValue;
|
||||
final String secondaryValue;
|
||||
final Color color;
|
||||
final int hoursInterval;
|
||||
final void Function() onTapTitle;
|
||||
final bool isDesktop;
|
||||
|
||||
const HomeChart({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.data,
|
||||
required this.label,
|
||||
required this.primaryValue,
|
||||
required this.secondaryValue,
|
||||
required this.color,
|
||||
required this.hoursInterval
|
||||
}) : super(key: key);
|
||||
required this.hoursInterval,
|
||||
required this.onTapTitle,
|
||||
required this.isDesktop,
|
||||
});
|
||||
|
||||
@override
|
||||
State<HomeChart> createState() => _HomeChartState();
|
||||
}
|
||||
|
||||
class _HomeChartState extends State<HomeChart> {
|
||||
bool _isHover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final bool isEmpty = data.every((i) => i == 0);
|
||||
final bool isEmpty = widget.data.every((i) => i == 0);
|
||||
|
||||
if (!(appConfigProvider.hideZeroValues == true && isEmpty == true)) {
|
||||
List<DateTime> dateTimes = [];
|
||||
DateTime currentDate = DateTime.now().subtract(Duration(hours: hoursInterval*data.length+1));
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
currentDate = currentDate.add(Duration(hours: hoursInterval));
|
||||
DateTime currentDate = DateTime.now().subtract(Duration(hours: widget.hoursInterval*widget.data.length+1));
|
||||
for (var i = 0; i < widget.data.length; i++) {
|
||||
currentDate = currentDate.add(Duration(hours: widget.hoursInterval));
|
||||
dateTimes.add(currentDate);
|
||||
}
|
||||
|
||||
|
@ -49,60 +61,87 @@ class HomeChart extends StatelessWidget {
|
|||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: isEmpty
|
||||
? CrossAxisAlignment.center
|
||||
: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (_) => setState(() => _isHover = true),
|
||||
onExit: (_) => setState(() => _isHover = false),
|
||||
child: GestureDetector(
|
||||
onTapDown: (_) => setState(() => _isHover = true),
|
||||
onTapUp: (_) => setState(() => _isHover = false),
|
||||
onTap: !isEmpty ? () => widget.onTapTitle () : null,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.label,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: _isHover && !isEmpty
|
||||
? Theme.of(context).colorScheme.onSurfaceVariant
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isEmpty) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
size: 20,
|
||||
color: _isHover && !isEmpty
|
||||
? Theme.of(context).colorScheme.onSurfaceVariant
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
)
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
!isEmpty
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
primaryValue,
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
),
|
||||
Text(
|
||||
secondaryValue,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: color
|
||||
),
|
||||
)
|
||||
],
|
||||
if (!isEmpty) Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
widget.primaryValue,
|
||||
style: TextStyle(
|
||||
color: widget.color,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
),
|
||||
Text(
|
||||
widget.secondaryValue,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: widget.color
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
children: [
|
||||
Text(
|
||||
primaryValue,
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
"($secondaryValue)",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: color
|
||||
),
|
||||
)
|
||||
],
|
||||
],
|
||||
),
|
||||
if (isEmpty && !widget.isDesktop) Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.show_chart_rounded,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
size: 16,
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.noData,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -110,13 +149,35 @@ class HomeChart extends StatelessWidget {
|
|||
width: double.maxFinite,
|
||||
height: 150,
|
||||
child: CustomLineChart(
|
||||
data: data,
|
||||
color: color,
|
||||
data: widget.data,
|
||||
color: widget.color,
|
||||
dates: dateTimes,
|
||||
daysInterval: hoursInterval == 24,
|
||||
daysInterval: widget.hoursInterval == 24,
|
||||
context: context,
|
||||
)
|
||||
),
|
||||
if (isEmpty && widget.isDesktop) SizedBox(
|
||||
height: 150,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.show_chart_rounded,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
size: 30,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.noDataChart,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -79,13 +79,13 @@ class CombinedHomeChart extends StatelessWidget {
|
|||
replacedSafeBrowsing: appConfigProvider.hideZeroValues == true
|
||||
? removeZero(statusProvider.serverStatus!.stats.replacedSafebrowsing) != null
|
||||
? CombinedChartItem(
|
||||
label: AppLocalizations.of(context)!.malwarePhisingBlocked,
|
||||
label: AppLocalizations.of(context)!.malwarePhishingBlocked,
|
||||
color: Colors.green,
|
||||
data: statusProvider.serverStatus!.stats.replacedSafebrowsing
|
||||
)
|
||||
: null
|
||||
: CombinedChartItem(
|
||||
label: AppLocalizations.of(context)!.malwarePhisingBlocked,
|
||||
label: AppLocalizations.of(context)!.malwarePhishingBlocked,
|
||||
color: Colors.green,
|
||||
data: statusProvider.serverStatus!.stats.replacedSafebrowsing
|
||||
) ,
|
||||
|
@ -104,51 +104,6 @@ class CombinedHomeChart extends StatelessWidget {
|
|||
) ,
|
||||
);
|
||||
|
||||
Widget legend({
|
||||
required String label,
|
||||
required Color color,
|
||||
required String primaryValue,
|
||||
String? secondaryValue
|
||||
}) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: color
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
primaryValue,
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
),
|
||||
if (secondaryValue != null) Text(
|
||||
secondaryValue,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: color
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final hoursInterval = statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1;
|
||||
|
||||
List<DateTime> dateTimes = [];
|
||||
|
@ -193,28 +148,28 @@ class CombinedHomeChart extends StatelessWidget {
|
|||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
legend(
|
||||
_Legend(
|
||||
label: data.totalQueries.label,
|
||||
color: data.totalQueries.color,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numDnsQueries, Platform.localeName),
|
||||
secondaryValue: "${doubleFormat(statusProvider.serverStatus!.stats.avgProcessingTime*1000, Platform.localeName)} ms",
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (data.blockedFilters != null) legend(
|
||||
if (data.blockedFilters != null) _Legend(
|
||||
label: data.blockedFilters!.label,
|
||||
color: data.blockedFilters!.color,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numBlockedFiltering, Platform.localeName),
|
||||
secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numBlockedFiltering/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (data.replacedSafeBrowsing != null) legend(
|
||||
if (data.replacedSafeBrowsing != null) _Legend(
|
||||
label: data.replacedSafeBrowsing!.label,
|
||||
color: data.replacedSafeBrowsing!.color,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numReplacedSafebrowsing, Platform.localeName),
|
||||
secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numReplacedSafebrowsing/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (data.replacedParental != null) legend(
|
||||
if (data.replacedParental != null) _Legend(
|
||||
label: data.replacedParental!.label,
|
||||
color: data.replacedParental!.color,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numReplacedParental, Platform.localeName),
|
||||
|
@ -259,28 +214,28 @@ class CombinedHomeChart extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
legend(
|
||||
_Legend(
|
||||
label: data.totalQueries.label,
|
||||
color: data.totalQueries.color,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numDnsQueries, Platform.localeName),
|
||||
secondaryValue: "${doubleFormat(statusProvider.serverStatus!.stats.avgProcessingTime*1000, Platform.localeName)} ms",
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (data.blockedFilters != null) legend(
|
||||
if (data.blockedFilters != null) _Legend(
|
||||
label: data.blockedFilters!.label,
|
||||
color: data.blockedFilters!.color,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numBlockedFiltering, Platform.localeName),
|
||||
secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numBlockedFiltering/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (data.replacedSafeBrowsing != null) legend(
|
||||
if (data.replacedSafeBrowsing != null) _Legend(
|
||||
label: data.replacedSafeBrowsing!.label,
|
||||
color: data.replacedSafeBrowsing!.color,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numReplacedSafebrowsing, Platform.localeName),
|
||||
secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numReplacedSafebrowsing/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (data.replacedParental != null) legend(
|
||||
if (data.replacedParental != null) _Legend(
|
||||
label: data.replacedParental!.label,
|
||||
color: data.replacedParental!.color,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numReplacedParental, Platform.localeName),
|
||||
|
@ -302,4 +257,60 @@ class CombinedHomeChart extends StatelessWidget {
|
|||
return const SizedBox();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Legend extends StatelessWidget {
|
||||
final String label;
|
||||
final Color color;
|
||||
final String primaryValue;
|
||||
final String? secondaryValue;
|
||||
|
||||
const _Legend({
|
||||
Key? key,
|
||||
required this.label,
|
||||
required this.color,
|
||||
required this.primaryValue,
|
||||
this.secondaryValue
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: color
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
primaryValue,
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
),
|
||||
if (secondaryValue != null) Text(
|
||||
secondaryValue!,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: color
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/home/management_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/home/management_modal/management_modal.dart';
|
||||
|
||||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
|
@ -28,6 +28,7 @@ class HomeFab extends StatelessWidget {
|
|||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => const ManagementModal(
|
||||
dialog: false,
|
||||
),
|
||||
|
|
|
@ -8,12 +8,14 @@ import 'package:provider/provider.dart';
|
|||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/home/server_status.dart';
|
||||
import 'package:adguard_home_manager/screens/home/top_items/top_items_lists.dart';
|
||||
import 'package:adguard_home_manager/screens/home/combined_chart.dart';
|
||||
import 'package:adguard_home_manager/screens/home/appbar.dart';
|
||||
import 'package:adguard_home_manager/screens/home/fab.dart';
|
||||
import 'package:adguard_home_manager/screens/home/top_items.dart';
|
||||
import 'package:adguard_home_manager/screens/home/chart.dart';
|
||||
|
||||
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/logs_provider.dart';
|
||||
import 'package:adguard_home_manager/functions/number_format.dart';
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||
|
@ -21,7 +23,7 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
|||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||
|
||||
class Home extends StatefulWidget {
|
||||
const Home({Key? key}) : super(key: key);
|
||||
const Home({super.key});
|
||||
|
||||
@override
|
||||
State<Home> createState() => _HomeState();
|
||||
|
@ -32,8 +34,14 @@ class _HomeState extends State<Home> {
|
|||
late bool isVisible;
|
||||
|
||||
@override
|
||||
initState(){
|
||||
Provider.of<StatusProvider>(context, listen: false).getServerStatus();
|
||||
initState() {
|
||||
final statusProvider = Provider.of<StatusProvider>(context, listen: false);
|
||||
statusProvider.getServerStatus(
|
||||
withLoadingIndicator: statusProvider.serverStatus != null ? false : true
|
||||
);
|
||||
|
||||
final clientsProvider = Provider.of<ClientsProvider>(context, listen: false);
|
||||
clientsProvider.fetchClients(updateLoading: false);
|
||||
|
||||
super.initState();
|
||||
|
||||
|
@ -58,230 +66,10 @@ class _HomeState extends State<Home> {
|
|||
Widget build(BuildContext context) {
|
||||
final statusProvider = Provider.of<StatusProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
final logsProvider = Provider.of<LogsProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
Widget loading() {
|
||||
return SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.loadingStatus,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget loadError() {
|
||||
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)!.errorLoadServerStatus,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> listItems() {
|
||||
return [
|
||||
ServerStatusWidget(serverStatus: statusProvider.serverStatus!),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Divider(
|
||||
thickness: 1,
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
if (appConfigProvider.combinedChartHome == false) Wrap(
|
||||
children: [
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 700 ? 0.5 : 1,
|
||||
child: HomeChart(
|
||||
data: statusProvider.serverStatus!.stats.dnsQueries,
|
||||
label: AppLocalizations.of(context)!.dnsQueries,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numDnsQueries, Platform.localeName),
|
||||
secondaryValue: "${doubleFormat(statusProvider.serverStatus!.stats.avgProcessingTime*1000, Platform.localeName)} ms",
|
||||
color: Colors.blue,
|
||||
hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1,
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 700 ? 0.5 : 1,
|
||||
child: HomeChart(
|
||||
data: statusProvider.serverStatus!.stats.blockedFiltering,
|
||||
label: AppLocalizations.of(context)!.blockedFilters,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numBlockedFiltering, Platform.localeName),
|
||||
secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numBlockedFiltering/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
||||
color: Colors.red,
|
||||
hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1,
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 700 ? 0.5 : 1,
|
||||
child: HomeChart(
|
||||
data: statusProvider.serverStatus!.stats.replacedSafebrowsing,
|
||||
label: AppLocalizations.of(context)!.malwarePhisingBlocked,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numReplacedSafebrowsing, Platform.localeName),
|
||||
secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numReplacedSafebrowsing/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
||||
color: Colors.green,
|
||||
hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1,
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 700 ? 0.5 : 1,
|
||||
child: HomeChart(
|
||||
data: statusProvider.serverStatus!.stats.replacedParental,
|
||||
label: AppLocalizations.of(context)!.blockedAdultWebsites,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numReplacedParental, Platform.localeName),
|
||||
secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numReplacedParental/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
||||
color: Colors.orange,
|
||||
hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
if (appConfigProvider.combinedChartHome == true) const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: CombinedHomeChart(),
|
||||
),
|
||||
|
||||
if (width <= 700) ...appConfigProvider.homeTopItemsOrder.asMap().entries.map((item) {
|
||||
Widget list() {
|
||||
switch (item.value) {
|
||||
case HomeTopItems.queriedDomains:
|
||||
return TopItems(
|
||||
label: AppLocalizations.of(context)!.topQueriedDomains,
|
||||
data: statusProvider.serverStatus!.stats.topQueriedDomains,
|
||||
type: 'topQueriedDomains',
|
||||
);
|
||||
|
||||
case HomeTopItems.blockedDomains:
|
||||
return TopItems(
|
||||
label: AppLocalizations.of(context)!.topBlockedDomains,
|
||||
data: statusProvider.serverStatus!.stats.topBlockedDomains,
|
||||
type: 'topBlockedDomains',
|
||||
);
|
||||
|
||||
case HomeTopItems.recurrentClients:
|
||||
return TopItems(
|
||||
label: AppLocalizations.of(context)!.topClients,
|
||||
data: statusProvider.serverStatus!.stats.topClients,
|
||||
type: 'topClients',
|
||||
clients: true,
|
||||
);
|
||||
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
list(),
|
||||
if (item.key < appConfigProvider.homeTopItemsOrder.length - 1) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Divider(
|
||||
thickness: 1,
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
]
|
||||
],
|
||||
);
|
||||
}),
|
||||
if (width > 700) Column(
|
||||
children: [
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
children: appConfigProvider.homeTopItemsOrder.map((item) {
|
||||
switch (item) {
|
||||
case HomeTopItems.queriedDomains:
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: TopItems(
|
||||
label: AppLocalizations.of(context)!.topQueriedDomains,
|
||||
data: statusProvider.serverStatus!.stats.topQueriedDomains,
|
||||
type: 'topQueriedDomains',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
case HomeTopItems.blockedDomains:
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: TopItems(
|
||||
label: AppLocalizations.of(context)!.topBlockedDomains,
|
||||
data: statusProvider.serverStatus!.stats.topBlockedDomains,
|
||||
type: 'topBlockedDomains',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
case HomeTopItems.recurrentClients:
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: TopItems(
|
||||
label: AppLocalizations.of(context)!.topClients,
|
||||
data: statusProvider.serverStatus!.stats.topClients,
|
||||
type: 'topClients',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
|
@ -304,7 +92,7 @@ class _HomeState extends State<Home> {
|
|||
displacement: 95,
|
||||
onRefresh: () async {
|
||||
final result = await statusProvider.getServerStatus();
|
||||
if (result == false) {
|
||||
if (mounted && result == false) {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.serverStatusNotRefreshed,
|
||||
|
@ -318,13 +106,153 @@ class _HomeState extends State<Home> {
|
|||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
if (statusProvider.loadStatus == LoadStatus.loading) SliverFillRemaining(
|
||||
child: loading(),
|
||||
child: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.loadingStatus,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
if (statusProvider.loadStatus == LoadStatus.loaded) SliverList.list(
|
||||
children: listItems()
|
||||
children: [
|
||||
ServerStatusWidget(serverStatus: statusProvider.serverStatus!),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Divider(
|
||||
thickness: 1,
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
if (appConfigProvider.combinedChartHome == false) Wrap(
|
||||
children: [
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 700 ? 0.5 : 1,
|
||||
child: HomeChart(
|
||||
data: statusProvider.serverStatus!.stats.dnsQueries,
|
||||
label: AppLocalizations.of(context)!.dnsQueries,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numDnsQueries, Platform.localeName),
|
||||
secondaryValue: "${doubleFormat(statusProvider.serverStatus!.stats.avgProcessingTime*1000, Platform.localeName)} ms",
|
||||
color: Colors.blue,
|
||||
hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1,
|
||||
onTapTitle: () {
|
||||
logsProvider.setSelectedResultStatus(
|
||||
value: "all",
|
||||
refetch: true
|
||||
);
|
||||
logsProvider.filterLogs();
|
||||
appConfigProvider.setSelectedScreen(2);
|
||||
},
|
||||
isDesktop: width > 700,
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 700 ? 0.5 : 1,
|
||||
child: HomeChart(
|
||||
data: statusProvider.serverStatus!.stats.blockedFiltering,
|
||||
label: AppLocalizations.of(context)!.blockedFilters,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numBlockedFiltering, Platform.localeName),
|
||||
secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numBlockedFiltering/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
||||
color: Colors.red,
|
||||
hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1,
|
||||
onTapTitle: () {
|
||||
logsProvider.setSelectedResultStatus(
|
||||
value: "blocked",
|
||||
refetch: true
|
||||
);
|
||||
appConfigProvider.setSelectedScreen(2);
|
||||
},
|
||||
isDesktop: width > 700,
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 700 ? 0.5 : 1,
|
||||
child: HomeChart(
|
||||
data: statusProvider.serverStatus!.stats.replacedSafebrowsing,
|
||||
label: AppLocalizations.of(context)!.malwarePhishingBlocked,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numReplacedSafebrowsing, Platform.localeName),
|
||||
secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numReplacedSafebrowsing/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
||||
color: Colors.green,
|
||||
hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1,
|
||||
onTapTitle: () {
|
||||
logsProvider.setSelectedResultStatus(
|
||||
value: "blocked_safebrowsing",
|
||||
refetch: true
|
||||
);
|
||||
appConfigProvider.setSelectedScreen(2);
|
||||
},
|
||||
isDesktop: width > 700,
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 700 ? 0.5 : 1,
|
||||
child: HomeChart(
|
||||
data: statusProvider.serverStatus!.stats.replacedParental,
|
||||
label: AppLocalizations.of(context)!.blockedAdultWebsites,
|
||||
primaryValue: intFormat(statusProvider.serverStatus!.stats.numReplacedParental, Platform.localeName),
|
||||
secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numReplacedParental/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
||||
color: Colors.orange,
|
||||
hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1,
|
||||
onTapTitle: () {
|
||||
logsProvider.setSelectedResultStatus(
|
||||
value: "blocked_parental",
|
||||
refetch: true
|
||||
);
|
||||
logsProvider.filterLogs();
|
||||
appConfigProvider.setSelectedScreen(2);
|
||||
},
|
||||
isDesktop: width > 700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
if (appConfigProvider.combinedChartHome == true) const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: CombinedHomeChart(),
|
||||
),
|
||||
|
||||
TopItemsLists(order: appConfigProvider.homeTopItemsOrder),
|
||||
],
|
||||
),
|
||||
if (statusProvider.loadStatus == LoadStatus.error) SliverFillRemaining(
|
||||
child: loadError(),
|
||||
child: 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)!.errorLoadServerStatus,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
|
|
|
@ -1,462 +0,0 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||
import 'package:adguard_home_manager/functions/compare_versions.dart';
|
||||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||
import 'package:adguard_home_manager/functions/format_time.dart';
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
|
||||
class ManagementModal extends StatefulWidget {
|
||||
final bool dialog;
|
||||
|
||||
const ManagementModal({
|
||||
Key? key,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ManagementModal> createState() => _ManagementModalState();
|
||||
}
|
||||
|
||||
class _ManagementModalState extends State<ManagementModal> with SingleTickerProviderStateMixin {
|
||||
late AnimationController animationController;
|
||||
late Animation<double> animation;
|
||||
final ExpandableController expandableController = ExpandableController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
expandableController.addListener(() async {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
if (expandableController.value == false) {
|
||||
animationController.animateTo(0);
|
||||
}
|
||||
else {
|
||||
animationController.animateBack(1);
|
||||
}
|
||||
});
|
||||
|
||||
animationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
vsync: this,
|
||||
)
|
||||
..addListener(() => setState(() => {}));
|
||||
animation = Tween(
|
||||
begin: 0.0,
|
||||
end: 0.5,
|
||||
).animate(CurvedAnimation(
|
||||
parent: animationController,
|
||||
curve: Curves.easeInOut
|
||||
));
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final statusProvider = Provider.of<StatusProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
void updateBlocking({
|
||||
required bool value,
|
||||
required String filter,
|
||||
int? time
|
||||
}) async {
|
||||
final result = await statusProvider.updateBlocking(
|
||||
block: filter,
|
||||
newStatus: value,
|
||||
time: time
|
||||
);
|
||||
if (result != null) {
|
||||
if (result != false) {
|
||||
appConfigProvider.addLog(result);
|
||||
}
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.invalidUsernamePassword,
|
||||
color: Colors.red
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void disableWithCountdown(int time) async {
|
||||
updateBlocking(value: false, filter: 'general', time: time);
|
||||
expandableController.toggle();
|
||||
}
|
||||
|
||||
Widget mainSwitch() {
|
||||
Widget topRow({required bool legacyMode}) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
if (legacyMode == false) ...[
|
||||
RotationTransition(
|
||||
turns: animation,
|
||||
child: Icon(
|
||||
Icons.keyboard_arrow_down_rounded,
|
||||
size: 26,
|
||||
color: statusProvider.serverStatus!.generalEnabled == true
|
||||
? Theme.of(context).colorScheme.onSurfaceVariant
|
||||
: Colors.grey,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.allProtections,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
if (statusProvider.serverStatus!.timeGeneralDisabled > 0) ...[
|
||||
const SizedBox(height: 2),
|
||||
if (statusProvider.currentDeadline != null) Text(
|
||||
"${AppLocalizations.of(context)!.remainingTime}: ${formatRemainingSeconds(statusProvider.remainingTime)}"
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Switch(
|
||||
value: statusProvider.serverStatus!.generalEnabled,
|
||||
onChanged: statusProvider.protectionsManagementProcess.contains('general') == false
|
||||
? (value) {
|
||||
if (value == false && expandableController.expanded == true && legacyMode == false) {
|
||||
expandableController.toggle();
|
||||
}
|
||||
updateBlocking(
|
||||
value: value,
|
||||
filter: legacyMode == true ? 'general_legacy' : 'general'
|
||||
);
|
||||
} : null,
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Widget bottomRow() {
|
||||
return Container(
|
||||
height: 40,
|
||||
margin: const EdgeInsets.only(top: 8),
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
ActionChip(
|
||||
label: Text(AppLocalizations.of(context)!.seconds(30)),
|
||||
onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true
|
||||
? () => disableWithCountdown(29000)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionChip(
|
||||
label: Text(AppLocalizations.of(context)!.minute(1)),
|
||||
onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true
|
||||
? () => disableWithCountdown(59000)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionChip(
|
||||
label: Text(AppLocalizations.of(context)!.minutes(10)),
|
||||
onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true
|
||||
? () => disableWithCountdown(599000)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionChip(
|
||||
label: Text(AppLocalizations.of(context)!.hour(1)),
|
||||
onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true
|
||||
? () => disableWithCountdown(3599000)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionChip(
|
||||
label: Text(AppLocalizations.of(context)!.hours(24)),
|
||||
onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true
|
||||
? () => disableWithCountdown(86399000)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: serverVersionIsAhead(
|
||||
currentVersion: statusProvider.serverStatus!.serverVersion,
|
||||
referenceVersion: 'v0.107.28',
|
||||
referenceVersionBeta: 'v0.108.0-b.33'
|
||||
) == true
|
||||
? ExpandableNotifier(
|
||||
controller: expandableController,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: InkWell(
|
||||
onTap: statusProvider.serverStatus!.generalEnabled == true && !statusProvider.protectionsManagementProcess.contains('general')
|
||||
? () => expandableController.toggle()
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 12
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
||||
),
|
||||
child: Expandable(
|
||||
theme: const ExpandableThemeData(
|
||||
animationDuration: Duration(milliseconds: 200),
|
||||
fadeCurve: Curves.ease
|
||||
),
|
||||
collapsed: topRow(legacyMode: false),
|
||||
expanded: Column(
|
||||
children: [
|
||||
topRow(legacyMode: false),
|
||||
bottomRow(),
|
||||
const SizedBox(height: 8)
|
||||
],
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
: Material(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: InkWell(
|
||||
onTap: statusProvider.protectionsManagementProcess.contains('general') == false
|
||||
? () => updateBlocking(
|
||||
value: !statusProvider.serverStatus!.generalEnabled,
|
||||
filter: 'general_legacy'
|
||||
) : null,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 12
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
color: Theme.of(context).primaryColor.withOpacity(0.1)
|
||||
),
|
||||
child: topRow(legacyMode: true)
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget smallSwitch(String label, IconData icon, bool value, Function(bool) onChange, bool disabled) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: disabled == false
|
||||
? () => onChange(!value)
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 44,
|
||||
vertical: 8
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Switch(
|
||||
value: value,
|
||||
onChanged: disabled == false
|
||||
? onChange
|
||||
: null,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget header() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: Icon(
|
||||
Icons.shield_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.manageServer,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> toggles() {
|
||||
return [
|
||||
mainSwitch(),
|
||||
Container(height: 10),
|
||||
smallSwitch(
|
||||
AppLocalizations.of(context)!.ruleFiltering,
|
||||
Icons.filter_list_rounded,
|
||||
statusProvider.serverStatus!.filteringEnabled,
|
||||
(value) => updateBlocking(value: value, filter: 'filtering'),
|
||||
statusProvider.protectionsManagementProcess.contains('filtering')
|
||||
),
|
||||
smallSwitch(
|
||||
AppLocalizations.of(context)!.safeBrowsing,
|
||||
Icons.vpn_lock_rounded,
|
||||
statusProvider.serverStatus!.safeBrowsingEnabled,
|
||||
(value) => updateBlocking(value: value, filter: 'safeBrowsing'),
|
||||
statusProvider.protectionsManagementProcess.contains('safeBrowsing')
|
||||
),
|
||||
smallSwitch(
|
||||
AppLocalizations.of(context)!.parentalFiltering,
|
||||
Icons.block,
|
||||
statusProvider.serverStatus!.parentalControlEnabled,
|
||||
(value) => updateBlocking(value: value, filter: 'parentalControl'),
|
||||
statusProvider.protectionsManagementProcess.contains('parentalControl')
|
||||
),
|
||||
smallSwitch(
|
||||
AppLocalizations.of(context)!.safeSearch,
|
||||
Icons.search_rounded,
|
||||
statusProvider.serverStatus!.safeSearchEnabled,
|
||||
(value) => updateBlocking(value: value, filter: 'safeSearch'),
|
||||
statusProvider.protectionsManagementProcess.contains('safeSearch')
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
if (widget.dialog == true) {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 400
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
header(),
|
||||
...toggles()
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.close),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (Platform.isIOS) const SizedBox(height: 16)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
topRight: Radius.circular(28)
|
||||
)
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
header(),
|
||||
...toggles()
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.close),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (Platform.isIOS) const SizedBox(height: 16)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
238
lib/screens/home/management_modal/main_switch.dart
Normal file
238
lib/screens/home/management_modal/main_switch.dart
Normal file
|
@ -0,0 +1,238 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/format_time.dart';
|
||||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||
|
||||
class MainSwitch extends StatelessWidget {
|
||||
final ExpandableController expandableController;
|
||||
final void Function({ required bool value, required String filter }) updateBlocking;
|
||||
final void Function(int) disableWithCountdown;
|
||||
final Animation<double> animation;
|
||||
|
||||
const MainSwitch({
|
||||
super.key,
|
||||
required this.expandableController,
|
||||
required this.updateBlocking,
|
||||
required this.disableWithCountdown,
|
||||
required this.animation,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final statusProvider = Provider.of<StatusProvider>(context);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: ExpandableNotifier(
|
||||
controller: expandableController,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: InkWell(
|
||||
onTap: statusProvider.serverStatus!.generalEnabled == true && !statusProvider.protectionsManagementProcess.contains('general')
|
||||
? () => expandableController.toggle()
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 12
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
||||
),
|
||||
child: Expandable(
|
||||
theme: const ExpandableThemeData(
|
||||
animationDuration: Duration(milliseconds: 200),
|
||||
fadeCurve: Curves.ease
|
||||
),
|
||||
collapsed: _TopRow(
|
||||
legacyMode: false,
|
||||
expandableController: expandableController,
|
||||
updateBlocking: updateBlocking,
|
||||
animation: animation,
|
||||
),
|
||||
expanded: Column(
|
||||
children: [
|
||||
_TopRow(
|
||||
legacyMode: false,
|
||||
expandableController: expandableController,
|
||||
updateBlocking: updateBlocking,
|
||||
animation: animation,
|
||||
),
|
||||
_BottomRow(
|
||||
disableWithCountdown: disableWithCountdown,
|
||||
),
|
||||
const SizedBox(height: 8)
|
||||
],
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TopRow extends StatelessWidget {
|
||||
final bool legacyMode;
|
||||
final ExpandableController expandableController;
|
||||
final void Function({ required bool value, required String filter }) updateBlocking;
|
||||
final Animation<double> animation;
|
||||
|
||||
const _TopRow({
|
||||
required this.legacyMode,
|
||||
required this.expandableController,
|
||||
required this.updateBlocking,
|
||||
required this.animation,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final statusProvider = Provider.of<StatusProvider>(context);
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
RotationTransition(
|
||||
turns: animation,
|
||||
child: Icon(
|
||||
Icons.keyboard_arrow_down_rounded,
|
||||
size: 26,
|
||||
color: statusProvider.serverStatus!.generalEnabled == true
|
||||
? Theme.of(context).colorScheme.onSurfaceVariant
|
||||
: Colors.grey,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.allProtections,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
if (statusProvider.serverStatus!.timeGeneralDisabled > 0) ...[
|
||||
const SizedBox(height: 2),
|
||||
if (statusProvider.currentDeadline != null) Text(
|
||||
"${AppLocalizations.of(context)!.remainingTime}: ${formatRemainingSeconds(statusProvider.remainingTime)}"
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Switch(
|
||||
value: statusProvider.serverStatus!.generalEnabled,
|
||||
onChanged: statusProvider.protectionsManagementProcess.contains('general') == false
|
||||
? (value) {
|
||||
if (value == false && expandableController.expanded == true && legacyMode == false) {
|
||||
expandableController.toggle();
|
||||
}
|
||||
updateBlocking(
|
||||
value: value,
|
||||
filter: 'general'
|
||||
);
|
||||
} : null,
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BottomRow extends StatefulWidget {
|
||||
final void Function(int) disableWithCountdown;
|
||||
|
||||
const _BottomRow({
|
||||
required this.disableWithCountdown,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_BottomRow> createState() => _BottomRowState();
|
||||
}
|
||||
|
||||
class _BottomRowState extends State<_BottomRow> {
|
||||
final _chipsScrollController = ScrollController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final statusProvider = Provider.of<StatusProvider>(context);
|
||||
|
||||
final textScale = MediaQuery.of(context).textScaler.scale(1);
|
||||
|
||||
return Container(
|
||||
height: Platform.isMacOS || Platform.isLinux || Platform.isWindows
|
||||
? 50 * textScale
|
||||
: 40 * textScale,
|
||||
margin: const EdgeInsets.only(top: 8),
|
||||
child: Scrollbar(
|
||||
controller: _chipsScrollController,
|
||||
thumbVisibility: Platform.isMacOS || Platform.isLinux || Platform.isWindows,
|
||||
interactive: Platform.isMacOS || Platform.isLinux || Platform.isWindows,
|
||||
thickness: Platform.isMacOS || Platform.isLinux || Platform.isWindows ? 8 : 0,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: Platform.isMacOS || Platform.isLinux || Platform.isWindows ? 16 : 0
|
||||
),
|
||||
child: ListView(
|
||||
controller: _chipsScrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
ActionChip(
|
||||
label: Text(AppLocalizations.of(context)!.seconds(30)),
|
||||
onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true
|
||||
? () => widget.disableWithCountdown(29000)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionChip(
|
||||
label: Text(AppLocalizations.of(context)!.minute(1)),
|
||||
onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true
|
||||
? () => widget.disableWithCountdown(59000)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionChip(
|
||||
label: Text(AppLocalizations.of(context)!.minutes(10)),
|
||||
onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true
|
||||
? () => widget.disableWithCountdown(599000)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionChip(
|
||||
label: Text(AppLocalizations.of(context)!.hour(1)),
|
||||
onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true
|
||||
? () => widget.disableWithCountdown(3599000)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionChip(
|
||||
label: Text(AppLocalizations.of(context)!.hours(24)),
|
||||
onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true
|
||||
? () => widget.disableWithCountdown(86399000)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
267
lib/screens/home/management_modal/management_modal.dart
Normal file
267
lib/screens/home/management_modal/management_modal.dart
Normal file
|
@ -0,0 +1,267 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/home/management_modal/main_switch.dart';
|
||||
import 'package:adguard_home_manager/screens/home/management_modal/small_switch.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
|
||||
class ManagementModal extends StatefulWidget {
|
||||
final bool dialog;
|
||||
|
||||
const ManagementModal({
|
||||
super.key,
|
||||
required this.dialog
|
||||
});
|
||||
|
||||
@override
|
||||
State<ManagementModal> createState() => _ManagementModalState();
|
||||
}
|
||||
|
||||
class _ManagementModalState extends State<ManagementModal> with SingleTickerProviderStateMixin {
|
||||
late AnimationController animationController;
|
||||
late Animation<double> animation;
|
||||
final ExpandableController expandableController = ExpandableController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
expandableController.addListener(() async {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
if (expandableController.value == false) {
|
||||
animationController.animateTo(0);
|
||||
}
|
||||
else {
|
||||
animationController.animateBack(1);
|
||||
}
|
||||
});
|
||||
|
||||
animationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
vsync: this,
|
||||
)
|
||||
..addListener(() => setState(() => {}));
|
||||
animation = Tween(
|
||||
begin: 0.0,
|
||||
end: 0.5,
|
||||
).animate(CurvedAnimation(
|
||||
parent: animationController,
|
||||
curve: Curves.easeInOut
|
||||
));
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final statusProvider = Provider.of<StatusProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
void updateBlocking({
|
||||
required bool value,
|
||||
required String filter,
|
||||
int? time
|
||||
}) async {
|
||||
final result = await statusProvider.updateBlocking(
|
||||
block: filter,
|
||||
newStatus: value,
|
||||
time: time
|
||||
);
|
||||
if (mounted && result == false) {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.invalidUsernamePassword,
|
||||
color: Colors.red
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void disableWithCountdown(int time) async {
|
||||
updateBlocking(value: false, filter: 'general', time: time);
|
||||
expandableController.toggle();
|
||||
}
|
||||
|
||||
if (widget.dialog == true) {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 400
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: _Modal(
|
||||
expandableController: expandableController,
|
||||
updateBlocking: updateBlocking,
|
||||
disableWithCountdown: disableWithCountdown,
|
||||
animation: animation,
|
||||
)
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.close),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (Platform.isIOS) const SizedBox(height: 16)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
topRight: Radius.circular(28)
|
||||
)
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: _Modal(
|
||||
expandableController: expandableController,
|
||||
updateBlocking: updateBlocking,
|
||||
disableWithCountdown: disableWithCountdown,
|
||||
animation: animation,
|
||||
)
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.close),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (Platform.isIOS) const SizedBox(height: 16)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Modal extends StatelessWidget {
|
||||
final ExpandableController expandableController;
|
||||
final void Function({ required bool value, required String filter }) updateBlocking;
|
||||
final void Function(int) disableWithCountdown;
|
||||
final Animation<double> animation;
|
||||
|
||||
const _Modal({
|
||||
required this.expandableController,
|
||||
required this.updateBlocking,
|
||||
required this.disableWithCountdown,
|
||||
required this.animation,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final statusProvider = Provider.of<StatusProvider>(context);
|
||||
|
||||
return Wrap(
|
||||
children: [
|
||||
// Header
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: Icon(
|
||||
Icons.shield_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.manageServer,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
MainSwitch(
|
||||
expandableController: expandableController,
|
||||
updateBlocking: updateBlocking,
|
||||
disableWithCountdown: disableWithCountdown,
|
||||
animation: animation,
|
||||
),
|
||||
Container(height: 10),
|
||||
SmallSwitch(
|
||||
label: AppLocalizations.of(context)!.ruleFiltering,
|
||||
icon: Icons.filter_list_rounded,
|
||||
value: statusProvider.serverStatus!.filteringEnabled,
|
||||
onChange: (value) => updateBlocking(value: value, filter: 'filtering'),
|
||||
disabled: statusProvider.protectionsManagementProcess.contains('filtering')
|
||||
),
|
||||
SmallSwitch(
|
||||
label: AppLocalizations.of(context)!.safeBrowsing,
|
||||
icon: Icons.vpn_lock_rounded,
|
||||
value: statusProvider.serverStatus!.safeBrowsingEnabled,
|
||||
onChange: (value) => updateBlocking(value: value, filter: 'safeBrowsing'),
|
||||
disabled: statusProvider.protectionsManagementProcess.contains('safeBrowsing')
|
||||
),
|
||||
SmallSwitch(
|
||||
label: AppLocalizations.of(context)!.parentalFiltering,
|
||||
icon: Icons.block,
|
||||
value: statusProvider.serverStatus!.parentalControlEnabled,
|
||||
onChange: (value) => updateBlocking(value: value, filter: 'parentalControl'),
|
||||
disabled: statusProvider.protectionsManagementProcess.contains('parentalControl')
|
||||
),
|
||||
SmallSwitch(
|
||||
label: AppLocalizations.of(context)!.safeSearch,
|
||||
icon: Icons.search_rounded,
|
||||
value: statusProvider.serverStatus!.safeSearchEnabled,
|
||||
onChange: (value) => updateBlocking(value: value, filter: 'safeSearch'),
|
||||
disabled: statusProvider.protectionsManagementProcess.contains('safeSearch')
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
65
lib/screens/home/management_modal/small_switch.dart
Normal file
65
lib/screens/home/management_modal/small_switch.dart
Normal file
|
@ -0,0 +1,65 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class SmallSwitch extends StatelessWidget {
|
||||
final String label;
|
||||
final IconData icon;
|
||||
final bool value;
|
||||
final void Function(bool) onChange;
|
||||
final bool disabled;
|
||||
|
||||
const SmallSwitch({
|
||||
Key? key,
|
||||
required this.label,
|
||||
required this.icon,
|
||||
required this.value,
|
||||
required this.onChange,
|
||||
required this.disabled,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: disabled == false
|
||||
? () => onChange(!value)
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 44,
|
||||
vertical: 8
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Switch(
|
||||
value: value,
|
||||
onChanged: disabled == false
|
||||
? onChange
|
||||
: null,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -9,17 +9,19 @@ class ServerStatusWidget extends StatelessWidget {
|
|||
final ServerStatus serverStatus;
|
||||
|
||||
const ServerStatusWidget({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.serverStatus,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
return Container(
|
||||
final textScaleFactor = MediaQuery.of(context).textScaler.scale(1);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.serverStatus,
|
||||
|
@ -30,40 +32,39 @@ class ServerStatusWidget extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
height: width > 700 ? 66 : 146,
|
||||
child: GridView(
|
||||
padding: const EdgeInsets.all(0),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: width > 700 ? 4 : 2,
|
||||
crossAxisSpacing: 10,
|
||||
mainAxisSpacing: 10,
|
||||
mainAxisExtent: 65
|
||||
),
|
||||
children: [
|
||||
StatusBox(
|
||||
icon: Icons.filter_list_rounded,
|
||||
label: AppLocalizations.of(context)!.ruleFilteringWidget,
|
||||
isEnabled: serverStatus.filteringEnabled
|
||||
),
|
||||
StatusBox(
|
||||
icon: Icons.vpn_lock_rounded,
|
||||
label: AppLocalizations.of(context)!.safeBrowsingWidget,
|
||||
isEnabled: serverStatus.safeBrowsingEnabled
|
||||
),
|
||||
StatusBox(
|
||||
icon: Icons.block,
|
||||
label: AppLocalizations.of(context)!.parentalFilteringWidget,
|
||||
isEnabled: serverStatus.parentalControlEnabled
|
||||
),
|
||||
StatusBox(
|
||||
icon: Icons.search_rounded,
|
||||
label: AppLocalizations.of(context)!.safeSearchWidget,
|
||||
isEnabled: serverStatus.safeSearchEnabled
|
||||
),
|
||||
],
|
||||
GridView(
|
||||
primary: false,
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.all(0),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: width > 700 ? 4 : 2,
|
||||
crossAxisSpacing: 10,
|
||||
mainAxisSpacing: 10,
|
||||
mainAxisExtent: 70*textScaleFactor
|
||||
),
|
||||
children: [
|
||||
StatusBox(
|
||||
icon: Icons.filter_list_rounded,
|
||||
label: AppLocalizations.of(context)!.ruleFilteringWidget,
|
||||
isEnabled: serverStatus.filteringEnabled
|
||||
),
|
||||
StatusBox(
|
||||
icon: Icons.vpn_lock_rounded,
|
||||
label: AppLocalizations.of(context)!.safeBrowsingWidget,
|
||||
isEnabled: serverStatus.safeBrowsingEnabled
|
||||
),
|
||||
StatusBox(
|
||||
icon: Icons.block,
|
||||
label: AppLocalizations.of(context)!.parentalFilteringWidget,
|
||||
isEnabled: serverStatus.parentalControlEnabled
|
||||
),
|
||||
StatusBox(
|
||||
icon: Icons.search_rounded,
|
||||
label: AppLocalizations.of(context)!.safeSearchWidget,
|
||||
isEnabled: serverStatus.safeSearchEnabled
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue