From 525facc538865b848c98cc3db8f72d4885ab728d Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 4 Oct 2023 19:20:43 +0200 Subject: [PATCH 001/177] Bug fixes --- lib/models/dhcp.dart | 4 +- lib/models/dns_info.dart | 2 +- lib/providers/servers_provider.dart | 14 +- lib/screens/home/home.dart | 2 +- lib/screens/home/management_modal.dart | 2 +- lib/screens/settings/dns/cache_config.dart | 2 +- lib/screens/top_items/top_items.dart | 2 +- lib/services/http_requests.dart | 258 ++++++++++++--------- lib/widgets/add_server_modal.dart | 6 + 9 files changed, 171 insertions(+), 121 deletions(-) diff --git a/lib/models/dhcp.dart b/lib/models/dhcp.dart index aaf4a42..c04ecc9 100644 --- a/lib/models/dhcp.dart +++ b/lib/models/dhcp.dart @@ -72,8 +72,8 @@ class DhcpStatus { factory DhcpStatus.fromJson(Map 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.from(json["leases"].map((x) => Lease.fromJson(x))), staticLeases: List.from(json["static_leases"].map((x) => Lease.fromJson(x))), enabled: json["enabled"], diff --git a/lib/models/dns_info.dart b/lib/models/dns_info.dart index 138efa3..5ab3cc2 100644 --- a/lib/models/dns_info.dart +++ b/lib/models/dns_info.dart @@ -12,7 +12,7 @@ class DnsInfo { int? cacheSize; int? cacheTtlMin; int? cacheTtlMax; - bool cacheOptimistic; + bool? cacheOptimistic; bool resolveClients; bool usePrivatePtrResolvers; List localPtrUpstreams; diff --git a/lib/providers/servers_provider.dart b/lib/providers/servers_provider.dart index c745260..601e149 100644 --- a/lib/providers/servers_provider.dart +++ b/lib/providers/servers_provider.dart @@ -169,12 +169,14 @@ class ServersProvider with ChangeNotifier { void checkServerUpdatesAvailable({ required Server server, + ApiClient? apiClient }) async { + final client = apiClient ?? _apiClient; setUpdateAvailableLoadStatus(LoadStatus.loading, true); - final result = await _apiClient!.checkServerUpdates(); + final result = await client!.checkServerUpdates(); if (result['result'] == 'success') { UpdateAvailableData data = UpdateAvailableData.fromJson(result['data']); - final gitHubResult = await _apiClient!.getUpdateChangelog(releaseTag: data.newVersion ?? data.currentVersion); + final gitHubResult = await client!.getUpdateChangelog(releaseTag: data.newVersion ?? data.currentVersion); if (gitHubResult['result'] == 'success') { data.changelog = gitHubResult['body']; } @@ -186,11 +188,12 @@ class ServersProvider with ChangeNotifier { } } - Future initializateServer(Server server) async { + Future initializateServer(Server server, ApiClient apiClient) async { final serverStatus = await _apiClient!.getServerStatus(); if (serverStatus['result'] == 'success') { checkServerUpdatesAvailable( // Do not await server: server, + apiClient: apiClient ); } } @@ -222,8 +225,9 @@ class ServersProvider with ChangeNotifier { if (defaultServer != null) { _selectedServer = defaultServer; - _apiClient = ApiClient(server: defaultServer); - initializateServer(defaultServer); + final client = ApiClient(server: defaultServer); + _apiClient = client; + initializateServer(defaultServer, client); } } else { diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index a37eee7..7acfa63 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -304,7 +304,7 @@ class _HomeState extends State { displacement: 95, onRefresh: () async { final result = await statusProvider.getServerStatus(); - if (result == false) { + if (mounted && result == false) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.serverStatusNotRefreshed, diff --git a/lib/screens/home/management_modal.dart b/lib/screens/home/management_modal.dart index aaeaab7..d23ddd4 100644 --- a/lib/screens/home/management_modal.dart +++ b/lib/screens/home/management_modal.dart @@ -80,7 +80,7 @@ class _ManagementModalState extends State with SingleTickerProv newStatus: value, time: time ); - if (result != null) { + if (mounted && result != null) { if (result != false) { appConfigProvider.addLog(result); } diff --git a/lib/screens/settings/dns/cache_config.dart b/lib/screens/settings/dns/cache_config.dart index cd2968b..eb5aad3 100644 --- a/lib/screens/settings/dns/cache_config.dart +++ b/lib/screens/settings/dns/cache_config.dart @@ -58,7 +58,7 @@ class _CacheConfigDnsScreenState extends State { cacheSizeController.text = dnsProvider.dnsInfo!.cacheSize.toString(); overrideMinTtlController.text = dnsProvider.dnsInfo!.cacheTtlMin.toString(); overrideMaxTtlController.text = dnsProvider.dnsInfo!.cacheTtlMax.toString(); - optimisticCache = dnsProvider.dnsInfo!.cacheOptimistic; + optimisticCache = dnsProvider.dnsInfo!.cacheOptimistic ?? false; validData = true; super.initState(); } diff --git a/lib/screens/top_items/top_items.dart b/lib/screens/top_items/top_items.dart index 6e9af74..f536b47 100644 --- a/lib/screens/top_items/top_items.dart +++ b/lib/screens/top_items/top_items.dart @@ -130,7 +130,7 @@ class _TopItemsScreenState extends State { body: RefreshIndicator( onRefresh: () async { final result = await statusProvider.getServerStatus(); - if (result == false) { + if (mounted && result == false) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.serverStatusNotRefreshed, diff --git a/lib/services/http_requests.dart b/lib/services/http_requests.dart index 85de805..c2b2c59 100644 --- a/lib/services/http_requests.dart +++ b/lib/services/http_requests.dart @@ -20,6 +20,7 @@ import 'package:adguard_home_manager/models/clients.dart'; import 'package:adguard_home_manager/models/clients_allowed_blocked.dart'; import 'package:adguard_home_manager/models/server.dart'; import 'package:adguard_home_manager/constants/urls.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; Future> apiRequest({ @@ -270,80 +271,107 @@ class ApiClient { }); Future getServerVersion() async { - final result = await apiRequest( - server: server, - method: 'get', - urlPath: '/status', - type: 'get_server_version' - ); + try { + final result = await apiRequest( + server: server, + method: 'get', + urlPath: '/status', + type: 'get_server_version' + ); - if (result['hasResponse'] == true) { - if (result['statusCode'] == 200 && result['body'] != null) { - return { - 'result': 'success', - 'data': jsonDecode(result['body'])['version'] - }; + if (result['hasResponse'] == true) { + if (result['statusCode'] == 200 && result['body'] != null) { + return { + 'result': 'success', + 'data': jsonDecode(result['body'])['version'] + }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'get_server_version', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result['statusCode'].toString(), + resBody: result['body'] + ) + }; + } } else { - return { - 'result': 'error', - 'log': AppLog( - type: 'get_server_version', - dateTime: DateTime.now(), - message: 'error_code_not_expected', - statusCode: result['statusCode'].toString(), - resBody: result['body'] - ) - }; + return result; } - } - else { - return result; + } catch (e) { + Sentry.captureException(e); + return { + 'result': 'error', + 'log': AppLog( + type: 'get_server_version', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + resBody: e.toString() + ) + }; } } Future getServerStatus() async { - final result = await Future.wait([ - apiRequest(server: server, method: 'get', urlPath: '/stats', type: 'server_status'), - apiRequest(server: server, method: 'get', urlPath: '/status', type: 'server_status'), - apiRequest(server: server, method: 'get', urlPath: '/filtering/status', type: 'server_status'), - apiRequest(server: server, method: 'get', urlPath: '/safesearch/status', type: 'server_status'), - apiRequest(server: server, method: 'get', urlPath: '/safebrowsing/status', type: 'server_status'), - apiRequest(server: server, method: 'get', urlPath: '/parental/status', type: 'server_status'), - apiRequest(server: server, method: 'get', urlPath: '/clients', type: 'server_status'), - ]); + try { + final result = await Future.wait([ + apiRequest(server: server, method: 'get', urlPath: '/stats', type: 'server_status'), + apiRequest(server: server, method: 'get', urlPath: '/status', type: 'server_status'), + apiRequest(server: server, method: 'get', urlPath: '/filtering/status', type: 'server_status'), + apiRequest(server: server, method: 'get', urlPath: '/safesearch/status', type: 'server_status'), + apiRequest(server: server, method: 'get', urlPath: '/safebrowsing/status', type: 'server_status'), + apiRequest(server: server, method: 'get', urlPath: '/parental/status', type: 'server_status'), + apiRequest(server: server, method: 'get', urlPath: '/clients', type: 'server_status'), + ]); - if ( - result[0]['hasResponse'] == true && - result[1]['hasResponse'] == true && - result[2]['hasResponse'] == true && - result[3]['hasResponse'] == true && - result[4]['hasResponse'] == true && - result[5]['hasResponse'] == true && - result[6]['hasResponse'] == true - ) { if ( - result[0]['statusCode'] == 200 && - result[1]['statusCode'] == 200 && - result[2]['statusCode'] == 200 && - result[3]['statusCode'] == 200 && - result[4]['statusCode'] == 200 && - result[5]['statusCode'] == 200 && - result[6]['statusCode'] == 200 + result[0]['hasResponse'] == true && + result[1]['hasResponse'] == true && + result[2]['hasResponse'] == true && + result[3]['hasResponse'] == true && + result[4]['hasResponse'] == true && + result[5]['hasResponse'] == true && + result[6]['hasResponse'] == true ) { - final Map mappedData = { - 'stats': jsonDecode(result[0]['body']), - 'clients': jsonDecode(result[6]['body'])['clients'], - 'status': jsonDecode(result[1]['body']), - 'filtering': jsonDecode(result[2]['body']), - 'safeSearch': jsonDecode(result[3]['body']), - 'safeBrowsingEnabled': jsonDecode(result[4]['body']), - 'parentalControlEnabled': jsonDecode(result[5]['body']), - }; - return { - 'result': 'success', - 'data': ServerStatus.fromJson(mappedData) - }; + if ( + result[0]['statusCode'] == 200 && + result[1]['statusCode'] == 200 && + result[2]['statusCode'] == 200 && + result[3]['statusCode'] == 200 && + result[4]['statusCode'] == 200 && + result[5]['statusCode'] == 200 && + result[6]['statusCode'] == 200 + ) { + final Map mappedData = { + 'stats': jsonDecode(result[0]['body']), + 'clients': jsonDecode(result[6]['body'])['clients'], + 'status': jsonDecode(result[1]['body']), + 'filtering': jsonDecode(result[2]['body']), + 'safeSearch': jsonDecode(result[3]['body']), + 'safeBrowsingEnabled': jsonDecode(result[4]['body']), + 'parentalControlEnabled': jsonDecode(result[5]['body']), + }; + return { + 'result': 'success', + 'data': ServerStatus.fromJson(mappedData) + }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'get_server_status', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result.map((res) => res['statusCode']).toString(), + resBody: result.map((res) => res['body']).toString() + ) + }; + } } else { return { @@ -351,24 +379,23 @@ class ApiClient { 'log': AppLog( type: 'get_server_status', dateTime: DateTime.now(), - message: 'error_code_not_expected', - statusCode: result.map((res) => res['statusCode']).toString(), - resBody: result.map((res) => res['body']).toString() + message: 'no_response', + statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), + resBody: result.map((res) => res['body'] ?? 'null').toString() ) }; } - } - else { + } catch (e) { + Sentry.captureException(e); return { - 'result': 'error', - 'log': AppLog( - type: 'get_server_status', - dateTime: DateTime.now(), - message: 'no_response', - statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), - resBody: result.map((res) => res['body'] ?? 'null').toString() - ) - }; + 'result': 'error', + 'log': AppLog( + type: 'get_server_status', + dateTime: DateTime.now(), + message: 'no_response', + resBody: e.toString() + ) + }; } } @@ -2004,34 +2031,48 @@ class ApiClient { } Future checkServerUpdates() async { - final result = await Future.wait([ - apiRequest( - urlPath: '/version.json', - method: 'post', - server: server, - type: 'check_server_updates', - body: { - "recheck_now": true - } - ), - apiRequest( - urlPath: '/status', - method: 'get', - server: server, - type: 'check_server_updates', - ), - ]); + try { + final result = await Future.wait([ + apiRequest( + urlPath: '/version.json', + method: 'post', + server: server, + type: 'check_server_updates', + body: { + "recheck_now": true + } + ), + apiRequest( + urlPath: '/status', + method: 'get', + server: server, + type: 'check_server_updates', + ), + ]); - if (result[0]['hasResponse'] == true && result[1]['hasResponse'] == true) { - if (result[0]['statusCode'] == 200 && result[1]['statusCode'] == 200) { - final Map obj = { - ...jsonDecode(result[0]['body']), - 'current_version': ServerInfoData.fromJson(jsonDecode(result[1]['body'])).version - }; - return { - 'result': 'success', - 'data': obj - }; + if (result[0]['hasResponse'] == true && result[1]['hasResponse'] == true) { + if (result[0]['statusCode'] == 200 && result[1]['statusCode'] == 200) { + final Map obj = { + ...jsonDecode(result[0]['body']), + 'current_version': ServerInfoData.fromJson(jsonDecode(result[1]['body'])).version + }; + return { + 'result': 'success', + 'data': obj + }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'get_filtering_status', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), + resBody: result.map((res) => res['body'] ?? 'null').toString(), + ) + }; + } } else { return { @@ -2039,22 +2080,21 @@ class ApiClient { 'log': AppLog( type: 'get_filtering_status', dateTime: DateTime.now(), - message: 'error_code_not_expected', + message: 'no_response', statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), resBody: result.map((res) => res['body'] ?? 'null').toString(), ) }; } - } - else { + } catch (e) { + Sentry.captureException(e); return { 'result': 'error', 'log': AppLog( type: 'get_filtering_status', dateTime: DateTime.now(), message: 'no_response', - statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), - resBody: result.map((res) => res['body'] ?? 'null').toString(), + resBody: e.toString(), ) }; } diff --git a/lib/widgets/add_server_modal.dart b/lib/widgets/add_server_modal.dart index 362eee9..c7604a1 100644 --- a/lib/widgets/add_server_modal.dart +++ b/lib/widgets/add_server_modal.dart @@ -256,6 +256,8 @@ class _AddServerModalState extends State { ? await loginHA(serverObj) : await login(serverObj); + if (!mounted) return; + if (result['result'] == 'success') { if (serverObj.user != null && serverObj.password != null) { serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!); @@ -268,6 +270,8 @@ class _AddServerModalState extends State { final serverStatus = await apiClient.getServerStatus(); + if (!mounted) return; + if (serverStatus['result'] == 'success') { statusProvider.setServerStatusData( data: serverStatus['data'] @@ -380,12 +384,14 @@ class _AddServerModalState extends State { ? await loginHA(serverObj) : await login(serverObj); + if (!mounted) return; if (result['result'] == 'success') { if (serverObj.user != null && serverObj.password != null) { serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!); } final serverSaved = await serversProvider.editServer(serverObj); + if (!mounted) return; if (serverSaved == null) { final ApiClient apiClient = ApiClient(server: serverObj); final version = await apiClient.getServerVersion(); From b2e8413559f7657b7fe37176917aba299f4097cd Mon Sep 17 00:00:00 2001 From: Zutzo <126886178+zutzo@users.noreply.github.com> Date: Thu, 5 Oct 2023 09:06:08 +0800 Subject: [PATCH 002/177] Update app_zh_CN.arb --- lib/l10n/app_zh_CN.arb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_zh_CN.arb b/lib/l10n/app_zh_CN.arb index f6a5eaf..796a6c6 100644 --- a/lib/l10n/app_zh_CN.arb +++ b/lib/l10n/app_zh_CN.arb @@ -653,5 +653,12 @@ "queries": "查询", "adultSites": "成人网站", "quickFilters": "状态过滤器", - "searchDomainInternet": "在互联网上搜索该域名" + "searchDomainInternet": "在互联网上搜索该域名", + "hideServerAddress": "隐藏服务器地址", + "hideServerAddressDescription": "在主页上隐藏服务器地址", + "topItemsOrder": "顶部项目顺序", + "topItemsOrderDescription": "排列主页顶部项目列表", + "topItemsReorderInfo": "按住并滑动一个项目以重新排序。", + "discardChanges": "放弃更改", + "discardChangesDescription": "您确定要放弃更改吗?" } From bc967c16527c0d583681ca9b557c47977514f10e Mon Sep 17 00:00:00 2001 From: Zutzo <126886178+zutzo@users.noreply.github.com> Date: Thu, 5 Oct 2023 09:07:13 +0800 Subject: [PATCH 003/177] Update app_zh.arb --- lib/l10n/app_zh.arb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index f6a5eaf..796a6c6 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -653,5 +653,12 @@ "queries": "查询", "adultSites": "成人网站", "quickFilters": "状态过滤器", - "searchDomainInternet": "在互联网上搜索该域名" + "searchDomainInternet": "在互联网上搜索该域名", + "hideServerAddress": "隐藏服务器地址", + "hideServerAddressDescription": "在主页上隐藏服务器地址", + "topItemsOrder": "顶部项目顺序", + "topItemsOrderDescription": "排列主页顶部项目列表", + "topItemsReorderInfo": "按住并滑动一个项目以重新排序。", + "discardChanges": "放弃更改", + "discardChangesDescription": "您确定要放弃更改吗?" } From 210606306ab63487fc4747696adcab829bc5bd66 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 5 Oct 2023 09:15:19 +0200 Subject: [PATCH 004/177] Fixed db create issue --- lib/providers/servers_provider.dart | 2 +- lib/services/db/database.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/providers/servers_provider.dart b/lib/providers/servers_provider.dart index 601e149..3a0e1ad 100644 --- a/lib/providers/servers_provider.dart +++ b/lib/providers/servers_provider.dart @@ -176,7 +176,7 @@ class ServersProvider with ChangeNotifier { final result = await client!.checkServerUpdates(); if (result['result'] == 'success') { UpdateAvailableData data = UpdateAvailableData.fromJson(result['data']); - final gitHubResult = await client!.getUpdateChangelog(releaseTag: data.newVersion ?? data.currentVersion); + final gitHubResult = await client.getUpdateChangelog(releaseTag: data.newVersion ?? data.currentVersion); if (gitHubResult['result'] == 'success') { data.changelog = gitHubResult['body']; } diff --git a/lib/services/db/database.dart b/lib/services/db/database.dart index 66bd269..4562b81 100644 --- a/lib/services/db/database.dart +++ b/lib/services/db/database.dart @@ -122,7 +122,7 @@ Future> loadDb(bool acceptsDynamicTheme) async { onCreate: (Database db, int version) async { await db.execute("CREATE TABLE servers (id TEXT PRIMARY KEY, name TEXT, connectionMethod TEXT, domain TEXT, path TEXT, port INTEGER, user TEXT, password TEXT, defaultServer INTEGER, authToken TEXT, runningOnHa INTEGER)"); await db.execute("CREATE TABLE appConfig (theme NUMERIC, overrideSslCheck NUMERIC, hideZeroValues NUMERIC, useDynamicColor NUMERIC, staticColor NUMERIC, useThemeColorForStatus NUMERIC, showTimeLogs NUMERIC, showIpLogs NUMERIC, combinedChart NUMERIC, doNotRememberVersion TEXT, hideServerAddress NUMERIC, homeTopItemsOrder TEXT)"); - await db.execute("INSERT INTO appConfig (theme, overrideSslCheck, hideZeroValues, useDynamicColor, staticColor, useThemeColorForStatus, showTimeLogs, showIpLogs, combinedChart, hideServerAddress, homeTopItemsOrder) VALUES (0, 0, 0, ${acceptsDynamicTheme == true ? 1 : 0}, 0, 0, 0, 0, 0, 0, $homeTopItemsDefaultOrderString)"); + await db.execute("INSERT INTO appConfig (theme, overrideSslCheck, hideZeroValues, useDynamicColor, staticColor, useThemeColorForStatus, showTimeLogs, showIpLogs, combinedChart, hideServerAddress, homeTopItemsOrder) VALUES (0, 0, 0, ${acceptsDynamicTheme == true ? 1 : 0}, 0, 0, 0, 0, 0, 0, '$homeTopItemsDefaultOrderString')"); }, onUpgrade: (Database db, int oldVersion, int newVersion) async { if (oldVersion == 1) { From b311dfe57ba12d1922db6cc0729609fce30dca28 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 5 Oct 2023 09:42:34 +0200 Subject: [PATCH 005/177] Improved database strings format --- lib/services/db/database.dart | 73 +++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/lib/services/db/database.dart b/lib/services/db/database.dart index 4562b81..5fed50f 100644 --- a/lib/services/db/database.dart +++ b/lib/services/db/database.dart @@ -120,9 +120,76 @@ Future> loadDb(bool acceptsDynamicTheme) async { 'adguard_home_manager.db', version: 9, onCreate: (Database db, int version) async { - await db.execute("CREATE TABLE servers (id TEXT PRIMARY KEY, name TEXT, connectionMethod TEXT, domain TEXT, path TEXT, port INTEGER, user TEXT, password TEXT, defaultServer INTEGER, authToken TEXT, runningOnHa INTEGER)"); - await db.execute("CREATE TABLE appConfig (theme NUMERIC, overrideSslCheck NUMERIC, hideZeroValues NUMERIC, useDynamicColor NUMERIC, staticColor NUMERIC, useThemeColorForStatus NUMERIC, showTimeLogs NUMERIC, showIpLogs NUMERIC, combinedChart NUMERIC, doNotRememberVersion TEXT, hideServerAddress NUMERIC, homeTopItemsOrder TEXT)"); - await db.execute("INSERT INTO appConfig (theme, overrideSslCheck, hideZeroValues, useDynamicColor, staticColor, useThemeColorForStatus, showTimeLogs, showIpLogs, combinedChart, hideServerAddress, homeTopItemsOrder) VALUES (0, 0, 0, ${acceptsDynamicTheme == true ? 1 : 0}, 0, 0, 0, 0, 0, 0, '$homeTopItemsDefaultOrderString')"); + await db.execute( + """ + CREATE TABLE + servers ( + id TEXT PRIMARY KEY, + name TEXT, + connectionMethod TEXT, + domain TEXT, + path TEXT, + port INTEGER, + user TEXT, + password TEXT, + defaultServer INTEGER, + authToken TEXT, + runningOnHa INTEGER + ) + """ + ); + + await db.execute( + """ + CREATE TABLE + appConfig ( + theme NUMERIC, + overrideSslCheck NUMERIC, + hideZeroValues NUMERIC, + useDynamicColor NUMERIC, + staticColor NUMERIC, + useThemeColorForStatus NUMERIC, + showTimeLogs NUMERIC, + showIpLogs NUMERIC, + combinedChart NUMERIC, + doNotRememberVersion TEXT, + hideServerAddress NUMERIC, + homeTopItemsOrder TEXT + ) + """ + ); + + await db.execute( + """ + INSERT INTO + appConfig ( + theme, + overrideSslCheck, + hideZeroValues, + useDynamicColor, + staticColor, + useThemeColorForStatus, + showTimeLogs, + showIpLogs, + combinedChart, + hideServerAddress, + homeTopItemsOrder + ) + VALUES ( + 0, + 0, + 0, + ${acceptsDynamicTheme == true ? 1 : 0}, + 0, + 0, + 0, + 0, + 0, + 0, + '$homeTopItemsDefaultOrderString' + ) + """ + ); }, onUpgrade: (Database db, int oldVersion, int newVersion) async { if (oldVersion == 1) { From 2389e345714cd0df7078f696c4dcb955f6b7b67e Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 7 Oct 2023 18:37:16 +0200 Subject: [PATCH 006/177] Refactor add server form --- lib/l10n/app_en.arb | 1 + lib/l10n/app_es.arb | 1 + lib/screens/connect/fab.dart | 35 +- lib/screens/servers/servers.dart | 35 +- .../add_server/add_server_functions.dart | 120 +++ lib/widgets/add_server/add_server_modal.dart | 561 +++++++++++++ lib/widgets/add_server/form_text_field.dart | 54 ++ lib/widgets/add_server_modal.dart | 766 ------------------ .../servers_list/servers_list_item.dart | 41 +- .../servers_list/servers_tile_item.dart | 41 +- 10 files changed, 749 insertions(+), 906 deletions(-) create mode 100644 lib/widgets/add_server/add_server_functions.dart create mode 100644 lib/widgets/add_server/add_server_modal.dart create mode 100644 lib/widgets/add_server/form_text_field.dart delete mode 100644 lib/widgets/add_server_modal.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 37e0acb..84a22f6 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -4,6 +4,7 @@ "connect": "Connect", "servers": "Servers", "createConnection": "Create connection", + "editConnection": "Edit connection", "name": "Name", "ipDomain": "IP address or domain", "path": "Path", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 4b2af05..9b97a3a 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -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", diff --git a/lib/screens/connect/fab.dart b/lib/screens/connect/fab.dart index 47cb65e..05ed1a2 100644 --- a/lib/screens/connect/fab.dart +++ b/lib/screens/connect/fab.dart @@ -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) })); } diff --git a/lib/screens/servers/servers.dart b/lib/screens/servers/servers.dart index 8a557fb..180af77 100644 --- a/lib/screens/servers/servers.dart +++ b/lib/screens/servers/servers.dart @@ -6,9 +6,8 @@ import 'package:flutter/rendering.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; -import 'package:adguard_home_manager/widgets/version_warning_modal.dart'; +import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; import 'package:adguard_home_manager/widgets/servers_list/servers_list.dart'; -import 'package:adguard_home_manager/widgets/add_server_modal.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; @@ -69,37 +68,7 @@ class _ServersState extends State { 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) })); } diff --git a/lib/widgets/add_server/add_server_functions.dart b/lib/widgets/add_server/add_server_functions.dart new file mode 100644 index 0000000..16a0149 --- /dev/null +++ b/lib/widgets/add_server/add_server_functions.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/models/server.dart'; +import 'package:adguard_home_manager/widgets/add_server/add_server_modal.dart'; +import 'package:adguard_home_manager/widgets/version_warning_modal.dart'; + +bool checkDataValid({ + required TextEditingController nameController, + required TextEditingController ipDomainController, + required String? ipDomainError, + required String? pathError, + required String? portError, +}) { + if ( + nameController.text != '' && + ipDomainController.text != '' && + ipDomainError == null && + pathError == null && + portError == null + ) { + return true; + } + else { + return false; + } +} + + +String? validatePort({ + required String? value, + required BuildContext context +}) { + if (value != null && value != '') { + if (int.tryParse(value) != null && int.parse(value) <= 65535) { + return null; + } + else { + return AppLocalizations.of(context)!.invalidPort; + } + } + else { + return null; + } +} + +String? validateSubroute({ + required BuildContext context, + required String? value +}) { + if (value != null && value != '') { + RegExp subrouteRegexp = RegExp(r'^\/\b([A-Za-z0-9_\-~/]*)[^\/|\.|\:]$'); + if (subrouteRegexp.hasMatch(value) == true) { + return null; + } + else { + return AppLocalizations.of(context)!.invalidPath; + } + } + else { + return null; + } +} + +String? validateAddress({ + required BuildContext context, + required String? value +}) { + if (value != null && value != '') { + RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$'); + RegExp domain = RegExp(r'^(([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+)|((\w|-)+)$'); + if (ipAddress.hasMatch(value) == true || domain.hasMatch(value) == true) { + return null; + } + else { + return AppLocalizations.of(context)!.invalidIpDomain; + } + } + else { + return AppLocalizations.of(context)!.ipDomainNotEmpty; + } +} + +void openServerFormModal({ + required BuildContext context, + required double width, + Server? server, +}) { + showGeneralDialog( + context: context, + barrierColor: width <= 700 + ?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) => AddServerModal( + fullScreen: width <= 700, + server: server, + onUnsupportedVersion: (version) => showDialog( + context: context, + builder: (ctx) => VersionWarningModal( + version: version + ), + barrierDismissible: false + ), + ), + ); +} \ No newline at end of file diff --git a/lib/widgets/add_server/add_server_modal.dart b/lib/widgets/add_server/add_server_modal.dart new file mode 100644 index 0000000..7eb8949 --- /dev/null +++ b/lib/widgets/add_server/add_server_modal.dart @@ -0,0 +1,561 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:provider/provider.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/add_server/form_text_field.dart'; +import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; + +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/constants/urls.dart'; +import 'package:adguard_home_manager/functions/open_url.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; +import 'package:adguard_home_manager/functions/base64.dart'; +import 'package:adguard_home_manager/services/http_requests.dart'; +import 'package:adguard_home_manager/models/app_log.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; +import 'package:adguard_home_manager/models/server.dart'; + +enum ConnectionType { http, https} + +class AddServerModal extends StatefulWidget { + final Server? server; + final bool fullScreen; + final void Function(String version) onUnsupportedVersion; + + const AddServerModal({ + Key? key, + this.server, + required this.fullScreen, + required this.onUnsupportedVersion + }) : super(key: key); + + @override + State createState() => _AddServerModalState(); +} + +class _AddServerModalState extends State { + final uuid = const Uuid(); + + final TextEditingController nameController = TextEditingController(); + String? nameError; + + ConnectionType connectionType = ConnectionType.http; + + final TextEditingController ipDomainController = TextEditingController(); + String? ipDomainError; + + final TextEditingController pathController = TextEditingController(); + String? pathError; + + final TextEditingController portController = TextEditingController(); + String? portError; + + final TextEditingController userController = TextEditingController(); + + final TextEditingController passwordController = TextEditingController(); + + bool defaultServer = false; + + bool homeAssistant = false; + + bool allDataValid = false; + + bool isConnecting = false; + + Widget sectionLabel(String label) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 24 + ), + child: Text( + label, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.primary + ), + ), + ); + } + + @override + void initState() { + if (widget.server != null) { + nameController.text = widget.server!.name; + connectionType = widget.server!.connectionMethod == 'https' ? ConnectionType.https : ConnectionType.http; + ipDomainController.text = widget.server!.domain; + pathController.text = widget.server!.path ?? ''; + portController.text = widget.server!.port != null ? widget.server!.port.toString() : ""; + userController.text = widget.server!.user ?? ""; + passwordController.text = widget.server!.password ?? ""; + defaultServer = widget.server!.defaultServer; + homeAssistant = widget.server!.runningOnHa; + } + setState(() => allDataValid = checkDataValid( + ipDomainController: ipDomainController, + nameController: nameController, + ipDomainError: ipDomainError, + pathError: pathError, + portError: portError + )); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final serversProvider = Provider.of(context, listen: false); + final statusProvider = Provider.of(context, listen: false); + final appConfigProvider = Provider.of(context, listen: false); + + void cancelConnecting() { + if (mounted) { + setState(() => isConnecting = false); + } + else { + isConnecting = false; + } + } + + void validateData() { + setState(() => allDataValid = checkDataValid( + ipDomainController: ipDomainController, + nameController: nameController, + ipDomainError: ipDomainError, + pathError: pathError, + portError: portError + )); + } + + String getErrorMessage(String message) { + if (message == 'invalid_username_password') return AppLocalizations.of(context)!.invalidUsernamePassword; + if (message == 'many_attempts') return AppLocalizations.of(context)!.tooManyAttempts; + if (message == 'no_connection') return AppLocalizations.of(context)!.cantReachServer; + if (message == 'server_error') return AppLocalizations.of(context)!.serverError; + return AppLocalizations.of(context)!.unknownError; + } + + void connect() async { + Server serverObj = Server( + id: uuid.v4(), + name: nameController.text, + connectionMethod: connectionType.name, + domain: ipDomainController.text, + port: portController.text != '' ? int.parse(portController.text) : null, + user: userController.text != "" ? userController.text : null, + password: passwordController.text != "" ? passwordController.text : null, + defaultServer: defaultServer, + authToken: homeAssistant == true + ? encodeBase64UserPass(userController.text, passwordController.text) + : null, + runningOnHa: homeAssistant + ); + setState(() => isConnecting = true); + + final result = homeAssistant == true + ? await loginHA(serverObj) + : await login(serverObj); + + // If something goes wrong with the connection + if (result['result'] != 'success') { + cancelConnecting(); + appConfigProvider.addLog(result['log']); + if (mounted) { + return showSnacbkar( + appConfigProvider: appConfigProvider, + label: getErrorMessage(result['result']), + color: Colors.red + ); + } + } + + if (serverObj.user != null && serverObj.password != null) { + serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!); + } + + final serverCreated = await serversProvider.createServer(serverObj); + + // If something goes wrong when saving the connection on the db + if (serverCreated == null) { + if (mounted) setState(() => isConnecting = false); + appConfigProvider.addLog( + AppLog( + type: 'save_connection_db', + dateTime: DateTime.now(), + message: serverCreated.toString() + ) + ); + if (mounted) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.connectionNotCreated, + color: Colors.red + ); + } + } + + statusProvider.setServerStatusLoad(LoadStatus.loading); + final ApiClient apiClient = ApiClient(server: serverObj); + final serverStatus = await apiClient.getServerStatus(); + + // If something goes wrong when fetching server status + if (serverStatus['result'] != 'success') { + appConfigProvider.addLog(serverStatus['log']); + statusProvider.setServerStatusLoad(LoadStatus.error); + Navigator.pop(context); + } + + // If everything is successful + statusProvider.setServerStatusData( + data: serverStatus['data'] + ); + serversProvider.setApiClient(apiClient); + statusProvider.setServerStatusLoad(LoadStatus.loaded); + if (serverStatus['data'].serverVersion.contains('a') || serverStatus['data'].serverVersion.contains('b')) { + Navigator.pop(context); + widget.onUnsupportedVersion(serverStatus['data'].serverVersion); + } + else { + Navigator.pop(context); + } + } + + void edit() async { + final Server serverObj = Server( + id: widget.server!.id, + name: nameController.text, + connectionMethod: connectionType.name, + domain: ipDomainController.text, + port: portController.text != '' ? int.parse(portController.text) : null, + user: userController.text != "" ? userController.text : null, + password: passwordController.text != "" ? passwordController.text : null, + defaultServer: defaultServer, + authToken: homeAssistant == true + ? encodeBase64UserPass(userController.text, passwordController.text) + : null, + runningOnHa: homeAssistant + ); + + final result = homeAssistant == true + ? await loginHA(serverObj) + : await login(serverObj); + + // If something goes wrong with the connection + if (result['result'] != 'success') { + cancelConnecting(); + appConfigProvider.addLog(result['log']); + if (mounted) { + return showSnacbkar( + appConfigProvider: appConfigProvider, + label: getErrorMessage(result['result']), + color: Colors.red + ); + } + } + + if (serverObj.user != null && serverObj.password != null) { + serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!); + } + final serverSaved = await serversProvider.editServer(serverObj); + + // If something goes wrong when saving the connection on the db + if (serverSaved == null) { + if (mounted) setState(() => isConnecting = false); + appConfigProvider.addLog( + AppLog( + type: 'save_connection_db', + dateTime: DateTime.now(), + message: serverSaved.toString() + ) + ); + if (mounted) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.connectionNotCreated, + color: Colors.red + ); + } + } + + // If everything is successful + final ApiClient apiClient = ApiClient(server: serverObj); + final version = await apiClient.getServerVersion(); + if ( + version['result'] == 'success' && + (version['data'].contains('a') || version['data'].contains('b')) // alpha or beta + ) { + Navigator.pop(context); + widget.onUnsupportedVersion(version['data']); + } + else { + Navigator.pop(context); + } + } + + Widget actions() { + return Row( + children: [ + IconButton( + onPressed: () => openUrl(Urls.connectionInstructions), + icon: const Icon(Icons.help_outline_outlined) + ), + IconButton( + tooltip: widget.server == null + ? AppLocalizations.of(context)!.connect + : AppLocalizations.of(context)!.save, + onPressed: allDataValid == true && isConnecting == false + ? widget.server == null + ? () => connect() + : () => edit() + : null, + icon: isConnecting + ? const CircularProgressIndicator() + : Icon( + widget.server == null + ? Icons.login_rounded + : Icons.save_rounded + ) + ), + ], + ); + } + + List form() { + return [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + margin: const EdgeInsets.only( + top: 24, + left: 24, + right: 24 + ), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary.withOpacity(0.05), + borderRadius: BorderRadius.circular(30), + border: Border.all( + color: Theme.of(context).colorScheme.primary + ) + ), + child: Text( + "${connectionType.name}://${ipDomainController.text}${portController.text != '' ? ':${portController.text}' : ""}${pathController.text}", + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.w500 + ), + ), + ), + sectionLabel(AppLocalizations.of(context)!.general), + FormTextField( + label: AppLocalizations.of(context)!.name, + controller: nameController, + icon: Icons.badge_rounded, + error: nameError, + onChanged: (value) { + if (value != '') { + setState(() => nameError = null); + } + else { + setState(() => nameError = AppLocalizations.of(context)!.nameNotEmpty); + } + validateData(); + }, + isConnecting: isConnecting, + ), + sectionLabel(AppLocalizations.of(context)!.connection), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: SegmentedButton( + segments: const [ + ButtonSegment( + value: ConnectionType.http, + label: Text("HTTP") + ), + ButtonSegment( + value: ConnectionType.https, + label: Text("HTTPS") + ), + ], + selected: {connectionType}, + onSelectionChanged: (value) => setState(() => connectionType = value.first), + ), + ), + const SizedBox(height: 30), + FormTextField( + label: AppLocalizations.of(context)!.ipDomain, + controller: ipDomainController, + icon: Icons.link_rounded, + error: ipDomainError, + keyboardType: TextInputType.url, + onChanged: (v) { + setState(() => ipDomainError = validateAddress(context: context, value: v)); + validateData(); + }, + isConnecting: isConnecting, + ), + const SizedBox(height: 20), + FormTextField( + label: AppLocalizations.of(context)!.path, + controller: pathController, + icon: Icons.route_rounded, + error: pathError, + onChanged: (v) { + setState(() => pathError = validateSubroute(context: context, value: v)); + validateData(); + }, + hintText: AppLocalizations.of(context)!.examplePath, + helperText: AppLocalizations.of(context)!.helperPath, + isConnecting: isConnecting, + ), + const SizedBox(height: 20), + FormTextField( + label: AppLocalizations.of(context)!.port, + controller: portController, + icon: Icons.numbers_rounded, + error: portError, + keyboardType: TextInputType.number, + onChanged: (v) { + setState(() => portError = validatePort(context: context, value: v)); + validateData(); + }, + isConnecting: isConnecting, + ), + sectionLabel(AppLocalizations.of(context)!.authentication), + FormTextField( + label: AppLocalizations.of(context)!.username, + controller: userController, + icon: Icons.person_rounded, + isConnecting: isConnecting, + ), + const SizedBox(height: 20), + FormTextField( + label: AppLocalizations.of(context)!.password, + controller: passwordController, + icon: Icons.lock_rounded, + keyboardType: TextInputType.visiblePassword, + obscureText: true, + isConnecting: isConnecting, + ), + sectionLabel(AppLocalizations.of(context)!.other), + Material( + color: Colors.transparent, + child: InkWell( + onTap: widget.server == null + ? () => setState(() => defaultServer = !defaultServer) + : null, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.defaultServer, + style: const TextStyle( + fontSize: 15, + ), + ), + Switch( + value: defaultServer, + onChanged: widget.server == null + ? (value) => setState(() => defaultServer = value) + : null, + ) + ], + ), + ), + ), + ), + const SizedBox(height: 20), + Material( + color: Colors.transparent, + child: InkWell( + onTap: () => setState(() => homeAssistant = !homeAssistant), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.runningHomeAssistant, + style: const TextStyle( + fontSize: 15, + ), + ), + Switch( + value: homeAssistant, + onChanged: (value) => setState(() => homeAssistant = value), + ) + ], + ), + ), + ), + ), + const SizedBox(height: 20), + ]; + } + + if (widget.fullScreen == true) { + return Dialog.fullscreen( + child: Scaffold( + appBar: AppBar( + title: widget.server != null + ? Text(AppLocalizations.of(context)!.createConnection) + : Text(AppLocalizations.of(context)!.editConnection), + actions: [ + actions(), + const SizedBox(width: 8) + ], + ), + body: ListView( + children: form() + ), + ), + ); + } + else { + return Dialog( + child: SizedBox( + width: 400, + 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( + AppLocalizations.of(context)!.createConnection, + style: const TextStyle( + fontSize: 20 + ), + ), + ], + ), + actions() + ], + ), + ), + Expanded( + child: ListView( + children: form() + ), + ) + ], + ), + ), + ); + } + } +} \ No newline at end of file diff --git a/lib/widgets/add_server/form_text_field.dart b/lib/widgets/add_server/form_text_field.dart new file mode 100644 index 0000000..0fb6f94 --- /dev/null +++ b/lib/widgets/add_server/form_text_field.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +class FormTextField extends StatelessWidget { + final TextEditingController controller; + final String label; + final String? error; + final IconData icon; + final TextInputType? keyboardType; + final Function(String)? onChanged; + final bool? obscureText; + final String? hintText; + final String? helperText; + final bool isConnecting; + + const FormTextField({ + Key? key, + required this.label, + required this.controller, + this.error, + required this.icon, + this.keyboardType, + this.onChanged, + this.obscureText, + this.hintText, + this.helperText, + required this.isConnecting + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextFormField( + controller: controller, + onChanged: onChanged, + obscureText: obscureText ?? false, + enabled: !isConnecting, + decoration: InputDecoration( + prefixIcon: Icon(icon), + errorText: error, + hintText: hintText, + helperText: helperText, + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: label, + ), + keyboardType: keyboardType, + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/add_server_modal.dart b/lib/widgets/add_server_modal.dart deleted file mode 100644 index c7604a1..0000000 --- a/lib/widgets/add_server_modal.dart +++ /dev/null @@ -1,766 +0,0 @@ -// ignore_for_file: use_build_context_synchronously - -import 'package:provider/provider.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/providers/app_config_provider.dart'; -import 'package:adguard_home_manager/functions/snackbar.dart'; -import 'package:adguard_home_manager/constants/urls.dart'; -import 'package:adguard_home_manager/functions/open_url.dart'; -import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/providers/status_provider.dart'; -import 'package:adguard_home_manager/functions/base64.dart'; -import 'package:adguard_home_manager/services/http_requests.dart'; -import 'package:adguard_home_manager/models/app_log.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; -import 'package:adguard_home_manager/models/server.dart'; - -enum ConnectionType { http, https} - -class AddServerModal extends StatefulWidget { - final Server? server; - final bool window; - final void Function(String version) onUnsupportedVersion; - - const AddServerModal({ - Key? key, - this.server, - required this.window, - required this.onUnsupportedVersion - }) : super(key: key); - - @override - State createState() => _AddServerModalState(); -} - -class _AddServerModalState extends State { - final uuid = const Uuid(); - - final TextEditingController nameController = TextEditingController(); - String? nameError; - - ConnectionType connectionType = ConnectionType.http; - - final TextEditingController ipDomainController = TextEditingController(); - String? ipDomainError; - - final TextEditingController pathController = TextEditingController(); - String? pathError; - - final TextEditingController portController = TextEditingController(); - String? portError; - - final TextEditingController userController = TextEditingController(); - - final TextEditingController passwordController = TextEditingController(); - - bool defaultServer = false; - - bool homeAssistant = false; - - bool allDataValid = false; - - bool isConnecting = false; - - Widget sectionLabel(String label) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 24 - ), - child: Text( - label, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.primary - ), - ), - ); - } - - Widget textField({ - required String label, - required TextEditingController controller, - String? error, - required IconData icon, - TextInputType? keyboardType, - Function(String)? onChanged, - bool? obscureText, - String? hintText, - String? helperText - }) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: TextFormField( - controller: controller, - onChanged: onChanged, - obscureText: obscureText ?? false, - decoration: InputDecoration( - prefixIcon: Icon(icon), - errorText: error, - hintText: hintText, - helperText: helperText, - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: label, - ), - keyboardType: keyboardType, - ), - ); - } - - void checkDataValid() { - if ( - nameController.text != '' && - ipDomainController.text != '' && - ipDomainError == null && - pathError == null && - portError == null - ) { - setState(() { - allDataValid = true; - }); - } - else { - setState(() { - allDataValid = false; - }); - } - } - - - void validatePort(String? value) { - if (value != null && value != '') { - if (int.tryParse(value) != null && int.parse(value) <= 65535) { - setState(() { - portError = null; - }); - } - else { - setState(() { - portError = AppLocalizations.of(context)!.invalidPort; - }); - } - } - else { - setState(() { - portError = null; - }); - } - checkDataValid(); - } - - void validateSubroute(String? value) { - if (value != null && value != '') { - RegExp subrouteRegexp = RegExp(r'^\/\b([A-Za-z0-9_\-~/]*)[^\/|\.|\:]$'); - if (subrouteRegexp.hasMatch(value) == true) { - setState(() { - pathError = null; - }); - } - else { - setState(() { - pathError = AppLocalizations.of(context)!.invalidPath; - }); - } - } - else { - setState(() { - pathError = null; - }); - } - checkDataValid(); - } - - void validateAddress(String? value) { - if (value != null && value != '') { - RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$'); - RegExp domain = RegExp(r'^(([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+)|((\w|-)+)$'); - if (ipAddress.hasMatch(value) == true || domain.hasMatch(value) == true) { - setState(() { - ipDomainError = null; - }); - } - else { - setState(() { - ipDomainError = AppLocalizations.of(context)!.invalidIpDomain; - }); - } - } - else { - setState(() { - ipDomainError = AppLocalizations.of(context)!.ipDomainNotEmpty; - }); - } - checkDataValid(); - } - - @override - void initState() { - if (widget.server != null) { - nameController.text = widget.server!.name; - connectionType = widget.server!.connectionMethod == 'https' ? ConnectionType.https : ConnectionType.http; - ipDomainController.text = widget.server!.domain; - pathController.text = widget.server!.path ?? ''; - portController.text = widget.server!.port != null ? widget.server!.port.toString() : ""; - userController.text = widget.server!.user ?? ""; - passwordController.text = widget.server!.password ?? ""; - defaultServer = widget.server!.defaultServer; - homeAssistant = widget.server!.runningOnHa; - } - checkDataValid(); - super.initState(); - } - - @override - Widget build(BuildContext context) { - final serversProvider = Provider.of(context, listen: false); - final statusProvider = Provider.of(context, listen: false); - final appConfigProvider = Provider.of(context, listen: false); - - final mediaQuery = MediaQuery.of(context); - - void cancelConnecting() { - if (mounted) { - setState(() => isConnecting = false); - } - else { - isConnecting = false; - } - } - - void connect() async { - Server serverObj = Server( - id: uuid.v4(), - name: nameController.text, - connectionMethod: connectionType.name, - domain: ipDomainController.text, - port: portController.text != '' ? int.parse(portController.text) : null, - user: userController.text != "" ? userController.text : null, - password: passwordController.text != "" ? passwordController.text : null, - defaultServer: defaultServer, - authToken: homeAssistant == true - ? encodeBase64UserPass(userController.text, passwordController.text) - : null, - runningOnHa: homeAssistant - ); - setState(() => isConnecting = true); - - final result = homeAssistant == true - ? await loginHA(serverObj) - : await login(serverObj); - - if (!mounted) return; - - if (result['result'] == 'success') { - if (serverObj.user != null && serverObj.password != null) { - serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!); - } - final serverCreated = await serversProvider.createServer(serverObj); - if (serverCreated == null) { - statusProvider.setServerStatusLoad(LoadStatus.loading); - - final ApiClient apiClient = ApiClient(server: serverObj); - - final serverStatus = await apiClient.getServerStatus(); - - if (!mounted) return; - - if (serverStatus['result'] == 'success') { - statusProvider.setServerStatusData( - data: serverStatus['data'] - ); - serversProvider.setApiClient(apiClient); - statusProvider.setServerStatusLoad(LoadStatus.loaded); - if (serverStatus['data'].serverVersion.contains('a') || serverStatus['data'].serverVersion.contains('b')) { - Navigator.pop(context); - widget.onUnsupportedVersion(serverStatus['data'].serverVersion); - } - else { - Navigator.pop(context); - } - } - else { - appConfigProvider.addLog(serverStatus['log']); - statusProvider.setServerStatusLoad(LoadStatus.error); - Navigator.pop(context); - } - } - else { - setState(() => isConnecting = false); - appConfigProvider.addLog( - AppLog( - type: 'save_connection_db', - dateTime: DateTime.now(), - message: serverCreated.toString() - ) - ); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.connectionNotCreated, - color: Colors.red - ); - } - } - else if (result['result'] == 'invalid_username_password') { - cancelConnecting(); - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.invalidUsernamePassword, - color: Colors.red - ); - } - else if (result['result'] == 'many_attempts') { - cancelConnecting(); - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.tooManyAttempts, - color: Colors.red - ); - } - else if (result['result'] == 'no_connection') { - cancelConnecting(); - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.cantReachServer, - color: Colors.red - ); - } - else if (result['result'] == 'ssl_error') { - cancelConnecting(); - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.sslError, - color: Colors.red - ); - } - else if (result['result'] == 'server_error') { - cancelConnecting(); - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.serverError, - color: Colors.red - ); - } - else { - cancelConnecting(); - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.unknownError, - color: Colors.red - ); - } - } - - void edit() async { - final Server serverObj = Server( - id: widget.server!.id, - name: nameController.text, - connectionMethod: connectionType.name, - domain: ipDomainController.text, - port: portController.text != '' ? int.parse(portController.text) : null, - user: userController.text != "" ? userController.text : null, - password: passwordController.text != "" ? passwordController.text : null, - defaultServer: defaultServer, - authToken: homeAssistant == true - ? encodeBase64UserPass(userController.text, passwordController.text) - : null, - runningOnHa: homeAssistant - ); - - final result = homeAssistant == true - ? await loginHA(serverObj) - : await login(serverObj); - - if (!mounted) return; - if (result['result'] == 'success') { - if (serverObj.user != null && serverObj.password != null) { - serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!); - } - final serverSaved = await serversProvider.editServer(serverObj); - - if (!mounted) return; - if (serverSaved == null) { - final ApiClient apiClient = ApiClient(server: serverObj); - final version = await apiClient.getServerVersion(); - if ( - version['result'] == 'success' && - (version['data'].contains('a') || version['data'].contains('b')) // alpha or beta - ) { - Navigator.pop(context); - widget.onUnsupportedVersion(version['data']); - } - else { - Navigator.pop(context); - } - } - else { - appConfigProvider.addLog( - AppLog( - type: 'edit_connection_db', - dateTime: DateTime.now(), - message: serverSaved.toString() - ) - ); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.connectionNotCreated, - color: Colors.red - ); - } - } - else if (result['result'] == 'invalid_username_password') { - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.invalidUsernamePassword, - color: Colors.red - ); - } - else if (result['result'] == 'many_attempts') { - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.tooManyAttempts, - color: Colors.red - ); - } - else if (result['result'] == 'no_connection') { - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.cantReachServer, - color: Colors.red - ); - } - else if (result['result'] == 'ssl_error') { - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.sslError, - color: Colors.red - ); - } - else if (result['result'] == 'server_error') { - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.serverError, - color: Colors.red - ); - } - else { - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.unknownError, - color: Colors.red - ); - } - } - - List form() { - return [ - Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - margin: const EdgeInsets.only( - top: 24, - left: 24, - right: 24 - ), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withOpacity(0.05), - borderRadius: BorderRadius.circular(30), - border: Border.all( - color: Theme.of(context).colorScheme.primary - ) - ), - child: Text( - "${connectionType.name}://${ipDomainController.text}${portController.text != '' ? ':${portController.text}' : ""}${pathController.text}", - textAlign: TextAlign.center, - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.w500 - ), - ), - ), - sectionLabel(AppLocalizations.of(context)!.general), - textField( - label: AppLocalizations.of(context)!.name, - controller: nameController, - icon: Icons.badge_rounded, - error: nameError, - onChanged: (value) { - if (value != '') { - setState(() => nameError = null); - } - else { - setState(() => nameError = AppLocalizations.of(context)!.nameNotEmpty); - } - checkDataValid(); - } - ), - sectionLabel(AppLocalizations.of(context)!.connection), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: SegmentedButton( - segments: const [ - ButtonSegment( - value: ConnectionType.http, - label: Text("HTTP") - ), - ButtonSegment( - value: ConnectionType.https, - label: Text("HTTPS") - ), - ], - selected: {connectionType}, - onSelectionChanged: (value) => setState(() => connectionType = value.first), - ), - ), - const SizedBox(height: 30), - textField( - label: AppLocalizations.of(context)!.ipDomain, - controller: ipDomainController, - icon: Icons.link_rounded, - error: ipDomainError, - keyboardType: TextInputType.url, - onChanged: validateAddress - ), - const SizedBox(height: 20), - textField( - label: AppLocalizations.of(context)!.path, - controller: pathController, - icon: Icons.route_rounded, - error: pathError, - onChanged: validateSubroute, - hintText: AppLocalizations.of(context)!.examplePath, - helperText: AppLocalizations.of(context)!.helperPath, - ), - const SizedBox(height: 20), - textField( - label: AppLocalizations.of(context)!.port, - controller: portController, - icon: Icons.numbers_rounded, - error: portError, - keyboardType: TextInputType.number, - onChanged: validatePort - ), - sectionLabel(AppLocalizations.of(context)!.authentication), - textField( - label: AppLocalizations.of(context)!.username, - controller: userController, - icon: Icons.person_rounded, - ), - const SizedBox(height: 20), - textField( - label: AppLocalizations.of(context)!.password, - controller: passwordController, - icon: Icons.lock_rounded, - keyboardType: TextInputType.visiblePassword, - obscureText: true - ), - sectionLabel(AppLocalizations.of(context)!.other), - Material( - color: Colors.transparent, - child: InkWell( - onTap: widget.server == null - ? () => setState(() => defaultServer = !defaultServer) - : null, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.defaultServer, - style: const TextStyle( - fontSize: 15, - ), - ), - Switch( - value: defaultServer, - onChanged: widget.server == null - ? (value) => setState(() => defaultServer = value) - : null, - ) - ], - ), - ), - ), - ), - const SizedBox(height: 20), - Material( - color: Colors.transparent, - child: InkWell( - onTap: () => setState(() => homeAssistant = !homeAssistant), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.runningHomeAssistant, - style: const TextStyle( - fontSize: 15, - ), - ), - Switch( - value: homeAssistant, - onChanged: (value) => setState(() => homeAssistant = value), - ) - ], - ), - ), - ), - ), - const SizedBox(height: 20), - ]; - } - - if (widget.window == true) { - return Dialog( - child: SizedBox( - width: 400, - 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( - AppLocalizations.of(context)!.createConnection, - style: const TextStyle( - fontSize: 20 - ), - ), - ], - ), - Row( - children: [ - IconButton( - onPressed: () => openUrl(Urls.connectionInstructions), - icon: const Icon(Icons.help_outline_outlined) - ), - IconButton( - tooltip: widget.server == null - ? AppLocalizations.of(context)!.connect - : AppLocalizations.of(context)!.save, - onPressed: allDataValid == true - ? widget.server == null - ? () => connect() - : () => edit() - : null, - icon: Icon( - widget.server == null - ? Icons.login_rounded - : Icons.save_rounded - ) - ), - ], - ), - ], - ), - ), - Expanded( - child: ListView( - children: form() - ), - ) - ], - ), - ), - ); - } - else { - return Stack( - children: [ - Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context)!.createConnection), - actions: [ - IconButton( - onPressed: () => openUrl(Urls.connectionInstructions), - icon: const Icon(Icons.help_outline_outlined) - ), - IconButton( - tooltip: widget.server == null - ? AppLocalizations.of(context)!.connect - : AppLocalizations.of(context)!.save, - onPressed: allDataValid == true - ? widget.server == null - ? () => connect() - : () => edit() - : null, - icon: Icon( - widget.server == null - ? Icons.login_rounded - : Icons.save_rounded - ) - ), - const SizedBox(width: 10) - ], - toolbarHeight: 70, - ), - body: ListView( - children: form(), - ) - ), - AnimatedOpacity( - opacity: isConnecting == true ? 1 : 0, - duration: const Duration(milliseconds: 250), - curve: Curves.easeInOut, - child: IgnorePointer( - ignoring: isConnecting == true ? false : true, - child: Scaffold( - backgroundColor: Colors.transparent, - body: Container( - width: mediaQuery.size.width, - height: mediaQuery.size.height, - color: const Color.fromRGBO(0, 0, 0, 0.7), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const CircularProgressIndicator( - color: Colors.white, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.connecting, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w500, - fontSize: 26 - ), - ) - ], - ), - ), - ), - ), - ) - ], - ); - } - } -} \ No newline at end of file diff --git a/lib/widgets/servers_list/servers_list_item.dart b/lib/widgets/servers_list/servers_list_item.dart index 724be22..280ae7c 100644 --- a/lib/widgets/servers_list/servers_list_item.dart +++ b/lib/widgets/servers_list/servers_list_item.dart @@ -5,8 +5,7 @@ import 'package:expandable/expandable.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/widgets/version_warning_modal.dart'; -import 'package:adguard_home_manager/widgets/add_server_modal.dart'; +import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; import 'package:adguard_home_manager/widgets/servers_list/delete_modal.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -88,41 +87,9 @@ class _ServersListItemState extends State with SingleTickerProv }); } - void openAddServerBottomSheet({Server? server}) async { + void openServerModal({Server? server}) async { await Future.delayed(const Duration(seconds: 0), (() => { - if (width > 700) { - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => AddServerModal( - server: server, - 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( - server: server, - window: false, - onUnsupportedVersion: (version) => showDialog( - context: context, - builder: (ctx) => VersionWarningModal( - version: version - ), - barrierDismissible: false - ), - ) - )) - } + openServerFormModal(context: context, width: width, server: server) })); } @@ -326,7 +293,7 @@ class _ServersListItemState extends State with SingleTickerProv ) ), PopupMenuItem( - onTap: (() => openAddServerBottomSheet(server: server)), + onTap: (() => openServerModal(server: server)), child: Row( children: [ const Icon(Icons.edit), diff --git a/lib/widgets/servers_list/servers_tile_item.dart b/lib/widgets/servers_list/servers_tile_item.dart index 1cbd622..8407526 100644 --- a/lib/widgets/servers_list/servers_tile_item.dart +++ b/lib/widgets/servers_list/servers_tile_item.dart @@ -4,8 +4,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/widgets/version_warning_modal.dart'; -import 'package:adguard_home_manager/widgets/add_server_modal.dart'; +import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; import 'package:adguard_home_manager/widgets/servers_list/delete_modal.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -57,41 +56,9 @@ class _ServersTileItemState extends State with SingleTickerProv }); } - void openAddServerBottomSheet({Server? server}) async { + void openServerModal({Server? server}) async { await Future.delayed(const Duration(seconds: 0), (() => { - if (width > 700) { - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => AddServerModal( - server: server, - 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( - server: server, - window: false, - onUnsupportedVersion: (version) => showDialog( - context: context, - builder: (ctx) => VersionWarningModal( - version: version - ), - barrierDismissible: false - ), - ) - )) - } + openServerFormModal(context: context, width: width, server: server) })); } @@ -291,7 +258,7 @@ class _ServersTileItemState extends State with SingleTickerProv ) ), PopupMenuItem( - onTap: (() => openAddServerBottomSheet(server: server)), + onTap: (() => openServerModal(server: server)), child: Row( children: [ const Icon(Icons.edit), From 36bd7acfedf1560e7f7d9147b9febf590b3b3e5b Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 7 Oct 2023 21:51:29 +0200 Subject: [PATCH 007/177] Improvements and fixes --- .../add_server/add_server_functions.dart | 2 +- lib/widgets/add_server/add_server_modal.dart | 134 ++++++++---------- 2 files changed, 59 insertions(+), 77 deletions(-) diff --git a/lib/widgets/add_server/add_server_functions.dart b/lib/widgets/add_server/add_server_functions.dart index 16a0149..6dd9c38 100644 --- a/lib/widgets/add_server/add_server_functions.dart +++ b/lib/widgets/add_server/add_server_functions.dart @@ -68,7 +68,7 @@ String? validateAddress({ }) { if (value != null && value != '') { RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$'); - RegExp domain = RegExp(r'^(([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+)|((\w|-)+)$'); + RegExp domain = RegExp(r'^(([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+)$'); if (ipAddress.hasMatch(value) == true || domain.hasMatch(value) == true) { return null; } diff --git a/lib/widgets/add_server/add_server_modal.dart b/lib/widgets/add_server/add_server_modal.dart index 7eb8949..294cd7d 100644 --- a/lib/widgets/add_server/add_server_modal.dart +++ b/lib/widgets/add_server/add_server_modal.dart @@ -6,6 +6,8 @@ import 'package:uuid/uuid.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/add_server/form_text_field.dart'; +import 'package:adguard_home_manager/widgets/section_label.dart'; +import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; @@ -67,23 +69,6 @@ class _AddServerModalState extends State { bool isConnecting = false; - Widget sectionLabel(String label) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 24 - ), - child: Text( - label, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.primary - ), - ), - ); - } - @override void initState() { if (widget.server != null) { @@ -166,12 +151,13 @@ class _AddServerModalState extends State { cancelConnecting(); appConfigProvider.addLog(result['log']); if (mounted) { - return showSnacbkar( + showSnacbkar( appConfigProvider: appConfigProvider, label: getErrorMessage(result['result']), color: Colors.red ); } + return; } if (serverObj.user != null && serverObj.password != null) { @@ -181,7 +167,7 @@ class _AddServerModalState extends State { final serverCreated = await serversProvider.createServer(serverObj); // If something goes wrong when saving the connection on the db - if (serverCreated == null) { + if (serverCreated != null) { if (mounted) setState(() => isConnecting = false); appConfigProvider.addLog( AppLog( @@ -197,6 +183,7 @@ class _AddServerModalState extends State { color: Colors.red ); } + return; } statusProvider.setServerStatusLoad(LoadStatus.loading); @@ -208,6 +195,7 @@ class _AddServerModalState extends State { appConfigProvider.addLog(serverStatus['log']); statusProvider.setServerStatusLoad(LoadStatus.error); Navigator.pop(context); + return; } // If everything is successful @@ -223,6 +211,7 @@ class _AddServerModalState extends State { else { Navigator.pop(context); } + return; } void edit() async { @@ -250,12 +239,13 @@ class _AddServerModalState extends State { cancelConnecting(); appConfigProvider.addLog(result['log']); if (mounted) { - return showSnacbkar( + showSnacbkar( appConfigProvider: appConfigProvider, label: getErrorMessage(result['result']), color: Colors.red ); } + return; } if (serverObj.user != null && serverObj.password != null) { @@ -264,7 +254,7 @@ class _AddServerModalState extends State { final serverSaved = await serversProvider.editServer(serverObj); // If something goes wrong when saving the connection on the db - if (serverSaved == null) { + if (serverSaved != null) { if (mounted) setState(() => isConnecting = false); appConfigProvider.addLog( AppLog( @@ -280,6 +270,7 @@ class _AddServerModalState extends State { color: Colors.red ); } + return; } // If everything is successful @@ -294,7 +285,8 @@ class _AddServerModalState extends State { } else { Navigator.pop(context); - } + } + return; } Widget actions() { @@ -314,7 +306,11 @@ class _AddServerModalState extends State { : () => edit() : null, icon: isConnecting - ? const CircularProgressIndicator() + ? const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator() + ) : Icon( widget.server == null ? Icons.login_rounded @@ -350,7 +346,10 @@ class _AddServerModalState extends State { ), ), ), - sectionLabel(AppLocalizations.of(context)!.general), + SectionLabel( + label: AppLocalizations.of(context)!.general, + padding: const EdgeInsets.all(24), + ), FormTextField( label: AppLocalizations.of(context)!.name, controller: nameController, @@ -367,7 +366,10 @@ class _AddServerModalState extends State { }, isConnecting: isConnecting, ), - sectionLabel(AppLocalizations.of(context)!.connection), + SectionLabel( + label: AppLocalizations.of(context)!.connection, + padding: const EdgeInsets.all(24), + ), Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: SegmentedButton( @@ -425,7 +427,10 @@ class _AddServerModalState extends State { }, isConnecting: isConnecting, ), - sectionLabel(AppLocalizations.of(context)!.authentication), + SectionLabel( + label: AppLocalizations.of(context)!.authentication, + padding: const EdgeInsets.all(24), + ), FormTextField( label: AppLocalizations.of(context)!.username, controller: userController, @@ -441,58 +446,32 @@ class _AddServerModalState extends State { obscureText: true, isConnecting: isConnecting, ), - sectionLabel(AppLocalizations.of(context)!.other), - Material( - color: Colors.transparent, - child: InkWell( - onTap: widget.server == null - ? () => setState(() => defaultServer = !defaultServer) - : null, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.defaultServer, - style: const TextStyle( - fontSize: 15, - ), - ), - Switch( - value: defaultServer, - onChanged: widget.server == null - ? (value) => setState(() => defaultServer = value) - : null, - ) - ], - ), - ), + SectionLabel( + label: AppLocalizations.of(context)!.other, + padding: const EdgeInsets.only( + top: 32, + left: 24, + bottom: 12 ), ), - const SizedBox(height: 20), - Material( - color: Colors.transparent, - child: InkWell( - onTap: () => setState(() => homeAssistant = !homeAssistant), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.runningHomeAssistant, - style: const TextStyle( - fontSize: 15, - ), - ), - Switch( - value: homeAssistant, - onChanged: (value) => setState(() => homeAssistant = value), - ) - ], - ), - ), + CustomSwitchListTile( + value: defaultServer, + onChanged: (value) => setState(() => defaultServer = value), + title: AppLocalizations.of(context)!.defaultServer, + disabled: widget.server != null || isConnecting, + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 4 + ), + ), + CustomSwitchListTile( + value: homeAssistant, + onChanged: (value) => setState(() => homeAssistant = value), + title: AppLocalizations.of(context)!.runningHomeAssistant, + disabled: widget.server != null || isConnecting, + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 4 ), ), const SizedBox(height: 20), @@ -503,6 +482,9 @@ class _AddServerModalState extends State { return Dialog.fullscreen( child: Scaffold( appBar: AppBar( + leading: CloseButton( + onPressed: () => Navigator.pop(context), + ), title: widget.server != null ? Text(AppLocalizations.of(context)!.createConnection) : Text(AppLocalizations.of(context)!.editConnection), From adaa75e7173e3b8174ecd623501c8aac232dca4e Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 7 Oct 2023 23:03:30 +0200 Subject: [PATCH 008/177] Refactor clients --- lib/screens/clients/added_list.dart | 7 +- .../{ => client}/active_client_tile.dart | 0 .../{ => client}/added_client_tile.dart | 14 +- .../client/blocked_services_section.dart | 114 +++ lib/screens/clients/client/client_screen.dart | 442 +++++++++ .../client/client_screen_functions.dart | 87 ++ .../clients/client/identifiers_section.dart | 103 +++ .../{ => client}/logs_list_client.dart | 0 .../{ => client}/remove_client_modal.dart | 0 .../{ => client}/safe_search_modal.dart | 0 .../clients/{ => client}/services_modal.dart | 0 lib/screens/clients/client/settings_tile.dart | 63 ++ .../clients/{ => client}/tags_modal.dart | 0 lib/screens/clients/client/tags_section.dart | 63 ++ .../client/upstream_servers_section.dart | 111 +++ lib/screens/clients/client_screen.dart | 838 ------------------ lib/screens/clients/clients.dart | 6 +- lib/screens/clients/clients_desktop_view.dart | 2 +- lib/screens/clients/clients_list.dart | 2 +- lib/screens/clients/fab.dart | 2 +- lib/screens/clients/search_clients.dart | 4 +- .../access_settings/clients_list.dart | 2 +- 22 files changed, 1009 insertions(+), 851 deletions(-) rename lib/screens/clients/{ => client}/active_client_tile.dart (100%) rename lib/screens/clients/{ => client}/added_client_tile.dart (96%) create mode 100644 lib/screens/clients/client/blocked_services_section.dart create mode 100644 lib/screens/clients/client/client_screen.dart create mode 100644 lib/screens/clients/client/client_screen_functions.dart create mode 100644 lib/screens/clients/client/identifiers_section.dart rename lib/screens/clients/{ => client}/logs_list_client.dart (100%) rename lib/screens/clients/{ => client}/remove_client_modal.dart (100%) rename lib/screens/clients/{ => client}/safe_search_modal.dart (100%) rename lib/screens/clients/{ => client}/services_modal.dart (100%) create mode 100644 lib/screens/clients/client/settings_tile.dart rename lib/screens/clients/{ => client}/tags_modal.dart (100%) create mode 100644 lib/screens/clients/client/tags_section.dart create mode 100644 lib/screens/clients/client/upstream_servers_section.dart delete mode 100644 lib/screens/clients/client_screen.dart diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index 6b7f33c..c49667d 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -9,9 +9,9 @@ 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.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'; @@ -205,6 +205,7 @@ class _AddedListState extends State { onTap: widget.onClientSelected, onLongPress: openOptionsModal, onEdit: openClientModal, + onDelete: openDeleteModal, splitView: widget.splitView, serverVersion: statusProvider.serverStatus!.serverVersion, ), diff --git a/lib/screens/clients/active_client_tile.dart b/lib/screens/clients/client/active_client_tile.dart similarity index 100% rename from lib/screens/clients/active_client_tile.dart rename to lib/screens/clients/client/active_client_tile.dart diff --git a/lib/screens/clients/added_client_tile.dart b/lib/screens/clients/client/added_client_tile.dart similarity index 96% rename from lib/screens/clients/added_client_tile.dart rename to lib/screens/clients/client/added_client_tile.dart index c63c816..356d7be 100644 --- a/lib/screens/clients/added_client_tile.dart +++ b/lib/screens/clients/client/added_client_tile.dart @@ -15,6 +15,7 @@ class AddedClientTile extends StatelessWidget { final void Function(Client) onTap; final void Function(Client) onLongPress; final void Function(Client) onEdit; + final void Function(Client) onDelete; final Client? selectedClient; final bool? splitView; final String serverVersion; @@ -25,6 +26,7 @@ class AddedClientTile extends StatelessWidget { required this.onTap, required this.onLongPress, required this.onEdit, + required this.onDelete, this.selectedClient, required this.splitView, required this.serverVersion @@ -43,13 +45,21 @@ class AddedClientTile extends StatelessWidget { child: ContextMenuArea( builder: (context) => [ CustomListTile( - title: AppLocalizations.of(context)!.seeDetails, - icon: Icons.file_open_rounded, + title: AppLocalizations.of(context)!.edit, + icon: Icons.edit_rounded, onTap: () { Navigator.pop(context); onEdit(client); } ), + CustomListTile( + title: AppLocalizations.of(context)!.delete, + icon: Icons.delete_rounded, + onTap: () { + Navigator.pop(context); + onDelete(client); + } + ), CustomListTile( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, diff --git a/lib/screens/clients/client/blocked_services_section.dart b/lib/screens/clients/client/blocked_services_section.dart new file mode 100644 index 0000000..37c01d3 --- /dev/null +++ b/lib/screens/clients/client/blocked_services_section.dart @@ -0,0 +1,114 @@ +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 blockedServices; + final void Function(List) onUpdatedBlockedServices; + final void Function(bool) onUpdateServicesGlobalSettings; + + const BlockedServicesSection({ + Key? key, + required this.useGlobalSettingsServices, + required this.blockedServices, + required this.onUpdatedBlockedServices, + required this.onUpdateServicesGlobalSettings + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + 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: () => onUpdateServicesGlobalSettings(!useGlobalSettingsServices), + 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: (value) => onUpdateServicesGlobalSettings(value), + ) + ], + ), + ), + ), + ), + ), + const SizedBox(height: 10), + 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: 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 + ), + ) + ] + ], + ) + ], + ), + ), + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/client/client_screen.dart b/lib/screens/clients/client/client_screen.dart new file mode 100644 index 0000000..5994767 --- /dev/null +++ b/lib/screens/clients/client/client_screen.dart @@ -0,0 +1,442 @@ +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/upstream_servers_section.dart'; +import 'package:adguard_home_manager/screens/clients/client/identifiers_section.dart'; +import 'package:adguard_home_manager/screens/clients/client/blocked_services_section.dart'; +import 'package:adguard_home_manager/screens/clients/client/tags_section.dart'; +import 'package:adguard_home_manager/screens/clients/client/settings_tile.dart'; +import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.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/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 createState() => _ClientScreenState(); +} + +class _ClientScreenState extends State { + final Uuid uuid = const Uuid(); + + bool validValues = false; + + TextEditingController nameController = TextEditingController(); + + List selectedTags = []; + + List> 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 blockedServices = []; + + List> upstreamServers = []; + + 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) { + 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(context); + final statusProvider = Provider.of(context); + + void createClient() { + final Client client = Client( + name: nameController.text, + ids: List.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.from(upstreamServers.map((e) => e['controller'].text)), + tags: selectedTags + ); + widget.onConfirm(client); + } + + 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 updateServicesGlobalSettings(bool value) { + if (value == true) { + setState(() { + blockedServices = []; + useGlobalSettingsServices = true; + }); + } + else if (value == false) { + setState(() { + useGlobalSettingsServices = false; + }); + } + } + + List actions() { + return [ + IconButton( + onPressed: validValues == true + ? () { + createClient(); + Navigator.pop(context); + } + : null, + 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), + ]; + } + + 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: (_) => setState(() { + validValues = checkValidValues( + identifiersControllers: identifiersControllers, + nameController: nameController + ); + }), + 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.all(24), + ), + TagsSection( + selectedTags: selectedTags, + onTagsSelected: (tags) => setState(() => selectedTags = tags) + ), + IdentifiersSection( + identifiersControllers: identifiersControllers, + onUpdateIdentifiersControllers: (c) => setState(() { + identifiersControllers = c; + validValues = checkValidValues( + nameController: nameController, + identifiersControllers: identifiersControllers + ); + }), + onCheckValidValues: () => setState(() { + validValues = checkValidValues( + identifiersControllers: identifiersControllers, + nameController: nameController + ); + }), + ), + 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: () => enableDisableGlobalSettingsFiltering(), + 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: (value) => enableDisableGlobalSettingsFiltering() + ) + ], + ), + ), + ), + ), + ), + const SizedBox(height: 10), + SettingsTile( + label: AppLocalizations.of(context)!.enableFiltering, + value: enableFiltering, + onChange: (value) => setState(() => enableFiltering = value), + useGlobalSettingsFiltering: useGlobalSettingsFiltering, + ), + SettingsTile( + label: AppLocalizations.of(context)!.enableSafeBrowsing, + value: enableSafeBrowsing, + onChange: (value) => setState(() => enableSafeBrowsing = value), + useGlobalSettingsFiltering: useGlobalSettingsFiltering, + ), + SettingsTile( + label: AppLocalizations.of(context)!.enableParentalControl, + value: enableParentalControl, + onChange: (value) => setState(() => enableParentalControl = value), + useGlobalSettingsFiltering: useGlobalSettingsFiltering, + ), + 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( + context: context, + blockedServices: blockedServices, + defaultSafeSearch: defaultSafeSearch, + safeSearch: safeSearch, + onUpdateSafeSearch: (s) => setState(() => safeSearch = s) + ) + : null, + ), + if ( + serverVersionIsAhead( + currentVersion: statusProvider.serverStatus!.serverVersion, + referenceVersion: 'v0.107.28', + referenceVersionBeta: 'v0.108.0-b.33' + ) == false + ) SettingsTile( + label: AppLocalizations.of(context)!.enableSafeSearch, + value: enableSafeSearch, + onChange: (value) => setState(() => enableSafeSearch = value), + useGlobalSettingsFiltering: useGlobalSettingsFiltering, + ), + SectionLabel( + label: AppLocalizations.of(context)!.blockedServices, + padding: const EdgeInsets.all(24), + ), + BlockedServicesSection( + useGlobalSettingsServices: useGlobalSettingsServices, + blockedServices: blockedServices, + onUpdatedBlockedServices: (s) => setState(() => blockedServices = s), + onUpdateServicesGlobalSettings: (v) => setState(() => useGlobalSettingsServices = v), + ), + UpstreamServersSection( + upstreamServers: upstreamServers, + onCheckValidValues: () => setState(() { + validValues = checkValidValues( + identifiersControllers: identifiersControllers, + nameController: nameController + ); + }), + onUpdateUpstreamServers: (v) => setState(() => upstreamServers = v) + ), + 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: [ + 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: 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: actions(), + ), + body: content(true) + ); + } + } +} \ No newline at end of file diff --git a/lib/screens/clients/client/client_screen_functions.dart b/lib/screens/clients/client/client_screen_functions.dart new file mode 100644 index 0000000..1bbfd24 --- /dev/null +++ b/lib/screens/clients/client/client_screen_functions.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.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/providers/clients_provider.dart'; +import 'package:adguard_home_manager/models/safe_search.dart'; + +void openTagsModal({ + required BuildContext context, + required List selectedTags, + required void Function(List) onSelectedTags +}) { + showDialog( + context: context, + builder: (context) => TagsModal( + selectedTags: selectedTags, + tags: Provider.of(context, listen: false).clients!.supportedTags, + onConfirm: onSelectedTags, + ) + ); +} + +void openServicesModal({ + required BuildContext context, + required List blockedServices, + required void Function(List) 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 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 + ) + ); +} + +bool checkValidValues({ + required TextEditingController nameController, + required List> identifiersControllers +}) { + if ( + nameController.text != '' && + identifiersControllers.isNotEmpty && + identifiersControllers[0]['controller']!.text != '' + ) { + return true; + } + else { + return false; + } +} diff --git a/lib/screens/clients/client/identifiers_section.dart b/lib/screens/clients/client/identifiers_section.dart new file mode 100644 index 0000000..08b044b --- /dev/null +++ b/lib/screens/clients/client/identifiers_section.dart @@ -0,0 +1,103 @@ +import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.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> identifiersControllers; + final void Function(List>) onUpdateIdentifiersControllers; + final void Function() onCheckValidValues; + + const IdentifiersSection({ + Key? key, + required this.identifiersControllers, + required this.onUpdateIdentifiersControllers, + required this.onCheckValidValues + }) : super(key: key); + + @override + State createState() => _IdentifiersSectionState(); +} + +class _IdentifiersSectionState extends State { + 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: 24, right: 24, top: 24, bottom: 12 + ) + ), + Padding( + padding: const EdgeInsets.only(right: 20), + child: IconButton( + onPressed: () => widget.onUpdateIdentifiersControllers([ + ...widget.identifiersControllers, + Map.from({ + 'id': uuid.v4(), + 'controller': TextEditingController() + }) + ]), + icon: const Icon(Icons.add) + ), + ) + ], + ), + if (widget.identifiersControllers.isNotEmpty) ...widget.identifiersControllers.map((controller) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + 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: 20), + Padding( + padding: const EdgeInsets.only(bottom: 25), + child: IconButton( + onPressed: () => widget.onUpdateIdentifiersControllers( + widget.identifiersControllers.where((e) => e['id'] != controller['id']).toList() + ), + icon: const Icon(Icons.remove_circle_outline_outlined) + ), + ) + ], + ), + )).toList(), + if (widget.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, + ), + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/logs_list_client.dart b/lib/screens/clients/client/logs_list_client.dart similarity index 100% rename from lib/screens/clients/logs_list_client.dart rename to lib/screens/clients/client/logs_list_client.dart diff --git a/lib/screens/clients/remove_client_modal.dart b/lib/screens/clients/client/remove_client_modal.dart similarity index 100% rename from lib/screens/clients/remove_client_modal.dart rename to lib/screens/clients/client/remove_client_modal.dart diff --git a/lib/screens/clients/safe_search_modal.dart b/lib/screens/clients/client/safe_search_modal.dart similarity index 100% rename from lib/screens/clients/safe_search_modal.dart rename to lib/screens/clients/client/safe_search_modal.dart diff --git a/lib/screens/clients/services_modal.dart b/lib/screens/clients/client/services_modal.dart similarity index 100% rename from lib/screens/clients/services_modal.dart rename to lib/screens/clients/client/services_modal.dart diff --git a/lib/screens/clients/client/settings_tile.dart b/lib/screens/clients/client/settings_tile.dart new file mode 100644 index 0000000..81f0c5c --- /dev/null +++ b/lib/screens/clients/client/settings_tile.dart @@ -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({ + Key? key, + required this.label, + required this.value, + this.onChange, + required this.useGlobalSettingsFiltering + }) : super(key: key); + + @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: 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, + ), + ), + ) + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/tags_modal.dart b/lib/screens/clients/client/tags_modal.dart similarity index 100% rename from lib/screens/clients/tags_modal.dart rename to lib/screens/clients/client/tags_modal.dart diff --git a/lib/screens/clients/client/tags_section.dart b/lib/screens/clients/client/tags_section.dart new file mode 100644 index 0000000..a6fa67c --- /dev/null +++ b/lib/screens/clients/client/tags_section.dart @@ -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 selectedTags; + final void Function(List) onTagsSelected; + + const TagsSection({ + Key? key, + required this.selectedTags, + required this.onTagsSelected + }) : super(key: key); + + @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: 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 + ), + ) + ], + ) + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/client/upstream_servers_section.dart b/lib/screens/clients/client/upstream_servers_section.dart new file mode 100644 index 0000000..47dd41d --- /dev/null +++ b/lib/screens/clients/client/upstream_servers_section.dart @@ -0,0 +1,111 @@ +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> upstreamServers; + final void Function() onCheckValidValues; + final void Function(List>) onUpdateUpstreamServers; + + const UpstreamServersSection({ + Key? key, + required this.upstreamServers, + required this.onCheckValidValues, + required this.onUpdateUpstreamServers + }) : super(key: key); + + @override + State createState() => _UpstreamServersSectionState(); +} + +class _UpstreamServersSectionState extends State { + 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.all(24), + ), + Padding( + padding: const EdgeInsets.only(right: 20), + child: IconButton( + onPressed: () => setState(() => widget.upstreamServers.add( + Map.from({ + 'id': uuid.v4(), + 'controller': TextEditingController() + }) + )), + icon: const Icon(Icons.add) + ), + ) + ], + ), + if (widget.upstreamServers.isNotEmpty) ...widget.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( + 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: 20), + IconButton( + onPressed: () => widget.onUpdateUpstreamServers( + widget.upstreamServers.where((e) => e['id'] != controller['id']).toList() + ), + icon: const Icon(Icons.remove_circle_outline_outlined) + ) + ], + ), + ), + )).toList(), + if (widget.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, + ), + ), + ], + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/client_screen.dart b/lib/screens/clients/client_screen.dart deleted file mode 100644 index c40a6d4..0000000 --- a/lib/screens/clients/client_screen.dart +++ /dev/null @@ -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 createState() => _ClientScreenState(); -} - -class _ClientScreenState extends State { - final Uuid uuid = const Uuid(); - bool editMode = true; - - bool validValues = false; - - TextEditingController nameController = TextEditingController(); - - List selectedTags = []; - - List> 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 blockedServices = []; - - List> 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(context); - final statusProvider = Provider.of(context); - - void createClient() { - final Client client = Client( - name: nameController.text, - ids: List.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.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.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.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) - ); - } - } -} \ No newline at end of file diff --git a/lib/screens/clients/clients.dart b/lib/screens/clients/clients.dart index 1dada10..4762e5b 100644 --- a/lib/screens/clients/clients.dart +++ b/lib/screens/clients/clients.dart @@ -7,7 +7,7 @@ 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/client/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'; @@ -195,7 +195,8 @@ class _ClientsState extends State with TickerProviderStateMixin { clientsProvider.setSearchTermClients(null); }); }, - icon: const Icon(Icons.arrow_back_rounded) + icon: const Icon(Icons.arrow_back_rounded), + tooltip: AppLocalizations.of(context)!.exitSearch, ), const SizedBox(width: 16), Expanded( @@ -223,6 +224,7 @@ class _ClientsState extends State with TickerProviderStateMixin { fontWeight: FontWeight.normal, fontSize: 18 ), + autofocus: true, ), ) ], diff --git a/lib/screens/clients/clients_desktop_view.dart b/lib/screens/clients/clients_desktop_view.dart index d6707ad..9e261ff 100644 --- a/lib/screens/clients/clients_desktop_view.dart +++ b/lib/screens/clients/clients_desktop_view.dart @@ -5,7 +5,7 @@ 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/client/logs_list_client.dart'; import 'package:adguard_home_manager/screens/clients/added_list.dart'; import 'package:adguard_home_manager/screens/clients/clients_list.dart'; diff --git a/lib/screens/clients/clients_list.dart b/lib/screens/clients/clients_list.dart index 4ecd98c..f3ee547 100644 --- a/lib/screens/clients/clients_list.dart +++ b/lib/screens/clients/clients_list.dart @@ -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'; diff --git a/lib/screens/clients/fab.dart b/lib/screens/clients/fab.dart index 8d73050..34dee54 100644 --- a/lib/screens/clients/fab.dart +++ b/lib/screens/clients/fab.dart @@ -6,7 +6,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/client_screen.dart'; +import 'package:adguard_home_manager/screens/clients/client/client_screen.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; diff --git a/lib/screens/clients/search_clients.dart b/lib/screens/clients/search_clients.dart index 4d2c754..a70923f 100644 --- a/lib/screens/clients/search_clients.dart +++ b/lib/screens/clients/search_clients.dart @@ -7,8 +7,8 @@ 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/client/remove_client_modal.dart'; +import 'package:adguard_home_manager/screens/clients/client/client_screen.dart'; import 'package:adguard_home_manager/screens/clients/options_modal.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; diff --git a/lib/screens/settings/access_settings/clients_list.dart b/lib/screens/settings/access_settings/clients_list.dart index 2466e11..31c01a7 100644 --- a/lib/screens/settings/access_settings/clients_list.dart +++ b/lib/screens/settings/access_settings/clients_list.dart @@ -8,7 +8,7 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/settings/access_settings/add_client_modal.dart'; -import 'package:adguard_home_manager/screens/clients/remove_client_modal.dart'; +import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart'; import 'package:adguard_home_manager/widgets/tab_content_list.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; From 5b715d04561788fc41cb5cdb606392b0c5d91ecb Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 7 Oct 2023 23:03:40 +0200 Subject: [PATCH 009/177] Autofocus search top items --- lib/screens/top_items/top_items.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/screens/top_items/top_items.dart b/lib/screens/top_items/top_items.dart index f536b47..26b84d5 100644 --- a/lib/screens/top_items/top_items.dart +++ b/lib/screens/top_items/top_items.dart @@ -85,6 +85,7 @@ class _TopItemsScreenState extends State { fontWeight: FontWeight.normal, fontSize: 18 ), + autofocus: true, ), ) : Text(widget.title), From 2ad99e737d7eec417a46bd109a0c2aaca4bc4b7c Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 7 Oct 2023 23:16:52 +0200 Subject: [PATCH 010/177] Change opening client modal --- lib/screens/clients/added_list.dart | 33 +++---------- lib/screens/clients/client/client_screen.dart | 46 +++++++++---------- .../client/client_screen_functions.dart | 39 ++++++++++++++++ .../clients/client/identifiers_section.dart | 1 - lib/screens/clients/fab.dart | 27 +++-------- lib/screens/clients/search_clients.dart | 35 +++----------- 6 files changed, 82 insertions(+), 99 deletions(-) diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index c49667d..08621ce 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -9,7 +9,7 @@ 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/client_screen.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'; @@ -128,31 +128,12 @@ class _AddedListState extends State { } 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) { diff --git a/lib/screens/clients/client/client_screen.dart b/lib/screens/clients/client/client_screen.dart index 5994767..4fcee53 100644 --- a/lib/screens/clients/client/client_screen.dart +++ b/lib/screens/clients/client/client_screen.dart @@ -20,18 +20,16 @@ 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; + final bool fullScreen; const ClientScreen({ Key? key, this.client, - required this.serverVersion, required this.onConfirm, this.onDelete, - required this.dialog + required this.fullScreen }) : super(key: key); @override @@ -81,7 +79,7 @@ class _ClientScreenState extends State { @override void initState() { version = serverVersionIsAhead( - currentVersion: widget.serverVersion, + currentVersion: Provider.of(context, listen: false).serverStatus!.serverVersion, referenceVersion: 'v0.107.28', referenceVersionBeta: 'v0.108.0-b.33' ); @@ -380,7 +378,26 @@ class _ClientScreenState extends State { } - if (widget.dialog == true) { + 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: content(true) + ), + ); + } + else { return Dialog( child: ConstrainedBox( constraints: const BoxConstraints( @@ -421,22 +438,5 @@ class _ClientScreenState extends State { ), ); } - 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: actions(), - ), - body: content(true) - ); - } } } \ No newline at end of file diff --git a/lib/screens/clients/client/client_screen_functions.dart b/lib/screens/clients/client/client_screen_functions.dart index 1bbfd24..4b84b2b 100644 --- a/lib/screens/clients/client/client_screen_functions.dart +++ b/lib/screens/clients/client/client_screen_functions.dart @@ -1,11 +1,15 @@ +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'; @@ -85,3 +89,38 @@ bool checkValidValues({ return false; } } + +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, + ), + ); +} \ No newline at end of file diff --git a/lib/screens/clients/client/identifiers_section.dart b/lib/screens/clients/client/identifiers_section.dart index 08b044b..3d9acf2 100644 --- a/lib/screens/clients/client/identifiers_section.dart +++ b/lib/screens/clients/client/identifiers_section.dart @@ -1,4 +1,3 @@ -import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart'; import 'package:flutter/material.dart'; import 'package:uuid/uuid.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; diff --git a/lib/screens/clients/fab.dart b/lib/screens/clients/fab.dart index 34dee54..f9697f6 100644 --- a/lib/screens/clients/fab.dart +++ b/lib/screens/clients/fab.dart @@ -8,6 +8,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.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/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/models/clients.dart'; @@ -51,27 +52,11 @@ 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( diff --git a/lib/screens/clients/search_clients.dart b/lib/screens/clients/search_clients.dart index a70923f..37544f8 100644 --- a/lib/screens/clients/search_clients.dart +++ b/lib/screens/clients/search_clients.dart @@ -1,14 +1,12 @@ // 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/client/remove_client_modal.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/options_modal.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; @@ -137,31 +135,12 @@ class _SearchClientsState extends State { } 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) { From 9d1ae22bdd88327eaac36c01712fb9c95714d0b5 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 00:05:38 +0200 Subject: [PATCH 011/177] Changes --- lib/screens/clients/added_list.dart | 4 +++- .../clients/client/added_client_tile.dart | 12 +++++----- lib/screens/clients/fab.dart | 22 ++++++++++--------- lib/screens/clients/options_modal.dart | 6 ++++- lib/screens/clients/search_clients.dart | 4 +++- 5 files changed, 29 insertions(+), 19 deletions(-) diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index 08621ce..8f27491 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -185,7 +185,9 @@ class _AddedListState extends State { client: widget.data[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, diff --git a/lib/screens/clients/client/added_client_tile.dart b/lib/screens/clients/client/added_client_tile.dart index 356d7be..9db4edb 100644 --- a/lib/screens/clients/client/added_client_tile.dart +++ b/lib/screens/clients/client/added_client_tile.dart @@ -14,7 +14,7 @@ class AddedClientTile extends StatelessWidget { 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; @@ -25,7 +25,7 @@ class AddedClientTile extends StatelessWidget { required this.client, required this.onTap, required this.onLongPress, - required this.onEdit, + this.onEdit, required this.onDelete, this.selectedClient, required this.splitView, @@ -44,12 +44,12 @@ class AddedClientTile extends StatelessWidget { borderRadius: BorderRadius.circular(28), child: ContextMenuArea( builder: (context) => [ - CustomListTile( + if (onEdit != null) CustomListTile( title: AppLocalizations.of(context)!.edit, icon: Icons.edit_rounded, onTap: () { Navigator.pop(context); - onEdit(client); + onEdit!(client); } ), CustomListTile( @@ -185,12 +185,12 @@ class AddedClientTile extends StatelessWidget { else { return ContextMenuArea( builder: (context) => [ - CustomListTile( + if (onEdit != null) CustomListTile( title: AppLocalizations.of(context)!.seeDetails, icon: Icons.file_open_rounded, onTap: () { Navigator.pop(context); - onEdit(client); + onEdit!(client); } ), CustomListTile( diff --git a/lib/screens/clients/fab.dart b/lib/screens/clients/fab.dart index f9697f6..ce291c2 100644 --- a/lib/screens/clients/fab.dart +++ b/lib/screens/clients/fab.dart @@ -1,19 +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/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 { @@ -21,8 +18,8 @@ class ClientsFab extends StatelessWidget { @override Widget build(BuildContext context) { - final appConfigProvider = Provider.of(context); final statusProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); final clientsProvider = Provider.of(context); final width = MediaQuery.of(context).size.width; @@ -59,9 +56,14 @@ class ClientsFab extends StatelessWidget { ); } - 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(); + } } } \ No newline at end of file diff --git a/lib/screens/clients/options_modal.dart b/lib/screens/clients/options_modal.dart index 61dea10..d779aae 100644 --- a/lib/screens/clients/options_modal.dart +++ b/lib/screens/clients/options_modal.dart @@ -1,6 +1,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/providers/status_provider.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile_dialog.dart'; class OptionsModal extends StatelessWidget { @@ -15,6 +17,8 @@ class OptionsModal extends StatelessWidget { @override Widget build(BuildContext context) { + final statusProvider = Provider.of(context); + return AlertDialog( contentPadding: const EdgeInsets.symmetric( horizontal: 0, @@ -39,7 +43,7 @@ class OptionsModal extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 24), - CustomListTileDialog( + if (statusProvider.serverStatus != null) CustomListTileDialog( onTap: () { Navigator.pop(context); onEdit(); diff --git a/lib/screens/clients/search_clients.dart b/lib/screens/clients/search_clients.dart index 37544f8..99d430c 100644 --- a/lib/screens/clients/search_clients.dart +++ b/lib/screens/clients/search_clients.dart @@ -231,7 +231,9 @@ class _SearchClientsState extends State { : const EdgeInsets.symmetric(horizontal: 20, vertical: 15), isThreeLine: true, onLongPress: () => openOptionsModal(clientsScreen[index]), - onTap: () => openClientModal(clientsScreen[index]), + onTap: statusProvider.serverStatus != null + ? () => openClientModal(clientsScreen[index]) + : null, title: Padding( padding: const EdgeInsets.only(bottom: 5), child: Text( From 7690d5f1bc88233bdf77f1b1667fc58a54111171 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 00:13:00 +0200 Subject: [PATCH 012/177] Refactor blocked services modal --- .../filters/blocked_services_screen.dart | 100 ++++++++++++------ lib/screens/filters/filters.dart | 23 +--- 2 files changed, 70 insertions(+), 53 deletions(-) diff --git a/lib/screens/filters/blocked_services_screen.dart b/lib/screens/filters/blocked_services_screen.dart index 75d941b..cefdda5 100644 --- a/lib/screens/filters/blocked_services_screen.dart +++ b/lib/screens/filters/blocked_services_screen.dart @@ -1,5 +1,7 @@ // 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'; @@ -12,11 +14,11 @@ 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; + final bool fullScreen; const BlockedServicesScreen({ Key? key, - required this.dialog + required this.fullScreen }) : super(key: key); @override @@ -180,7 +182,40 @@ class _BlockedServicesScreenStateWidget extends State { } } - 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)!.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() + ), + ), + ); + } + else { return Dialog( child: ConstrainedBox( constraints: const BoxConstraints( @@ -228,35 +263,34 @@ class _BlockedServicesScreenStateWidget extends State { ), ); } - 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() - ), - ); - } } +} + +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)), + ), + ); } \ No newline at end of file diff --git a/lib/screens/filters/filters.dart b/lib/screens/filters/filters.dart index 00cc214..c4e6d9b 100644 --- a/lib/screens/filters/filters.dart +++ b/lib/screens/filters/filters.dart @@ -149,26 +149,9 @@ class _FiltersState extends State { } } - 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); }); } @@ -312,7 +295,7 @@ class _FiltersState extends State { ) ), PopupMenuItem( - onTap: openBlockedServicesModal, + onTap: openBlockedServices, child: Row( children: [ const Icon(Icons.block), From 24dc69d0849a06013981279d1c0e2b708f8e95f7 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 00:14:53 +0200 Subject: [PATCH 013/177] Improvements --- lib/screens/filters/filters_triple_column.dart | 3 ++- lib/widgets/process_dialog.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/screens/filters/filters_triple_column.dart b/lib/screens/filters/filters_triple_column.dart index de0ffb0..2f7979d 100644 --- a/lib/screens/filters/filters_triple_column.dart +++ b/lib/screens/filters/filters_triple_column.dart @@ -276,7 +276,8 @@ class FiltersTripleColumn extends StatelessWidget { subtitleWidget: generateSubtitle(filteringProvider.filtering!.userRules[index]), trailing: IconButton( onPressed: () => onRemoveCustomRule(filteringProvider.filtering!.userRules[index]), - icon: const Icon(Icons.delete) + icon: const Icon(Icons.delete), + tooltip: AppLocalizations.of(context)!.delete, ), ), ), diff --git a/lib/widgets/process_dialog.dart b/lib/widgets/process_dialog.dart index e821696..0c91442 100644 --- a/lib/widgets/process_dialog.dart +++ b/lib/widgets/process_dialog.dart @@ -24,7 +24,7 @@ class ProcessDialog extends StatelessWidget { children: [ const CircularProgressIndicator(), const SizedBox(width: 40), - Expanded( + Flexible( child: Text( message, style: TextStyle( From 95aec4b3a5efe3c004b5db556fd2533bdde1d37d Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 00:19:32 +0200 Subject: [PATCH 014/177] Adapted more modals --- lib/screens/filters/add_button.dart | 43 ++++++++++--------- lib/screens/filters/add_custom_rule.dart | 53 +++++++++++++----------- 2 files changed, 51 insertions(+), 45 deletions(-) diff --git a/lib/screens/filters/add_button.dart b/lib/screens/filters/add_button.dart index 3cbedc9..195d7af 100644 --- a/lib/screens/filters/add_button.dart +++ b/lib/screens/filters/add_button.dart @@ -56,27 +56,30 @@ 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 { diff --git a/lib/screens/filters/add_custom_rule.dart b/lib/screens/filters/add_custom_rule.dart index dfb5753..4b79fc4 100644 --- a/lib/screens/filters/add_custom_rule.dart +++ b/lib/screens/filters/add_custom_rule.dart @@ -6,12 +6,12 @@ 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, required this.onConfirm, - required this.dialog + required this.fullScreen }) : super(key: key); @override @@ -328,7 +328,32 @@ class _AddCustomRuleState extends State { ]; } - 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: ListView( + children: content(), + ) + ), + ); + } + else { return Dialog( child: ConstrainedBox( constraints: const BoxConstraints( @@ -383,27 +408,5 @@ class _AddCustomRuleState extends State { ), ); } - 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(), - ) - ); - } } } \ No newline at end of file From bb5cdc13d1d95bdd867d319ecd98d1efaa99a6d3 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 00:26:48 +0200 Subject: [PATCH 015/177] Updated optionbox styles --- .../filters/update_interval_lists_modal.dart | 86 ++----------------- lib/widgets/option_box.dart | 28 +++--- 2 files changed, 26 insertions(+), 88 deletions(-) diff --git a/lib/screens/filters/update_interval_lists_modal.dart b/lib/screens/filters/update_interval_lists_modal.dart index c168793..deb714f 100644 --- a/lib/screens/filters/update_interval_lists_modal.dart +++ b/lib/screens/filters/update_interval_lists_modal.dart @@ -94,19 +94,7 @@ class _UpdateIntervalListsModalState extends State { 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), - ), - ), + label: AppLocalizations.of(context)!.never, ), ), ), @@ -118,19 +106,7 @@ class _UpdateIntervalListsModalState extends State { 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), - ), - ), + label: AppLocalizations.of(context)!.hour1, ), ), ), @@ -142,19 +118,7 @@ class _UpdateIntervalListsModalState extends State { 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), - ), - ), + label: AppLocalizations.of(context)!.hours12, ), ), ), @@ -166,19 +130,8 @@ class _UpdateIntervalListsModalState extends State { 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), - ), - ), + label: AppLocalizations.of(context)!.hours24, + ), ), ), @@ -190,19 +143,8 @@ class _UpdateIntervalListsModalState extends State { 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), - ), - ), + label: AppLocalizations.of(context)!.days3, + ), ), ), @@ -214,19 +156,7 @@ class _UpdateIntervalListsModalState extends State { 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), - ), - ), + label: AppLocalizations.of(context)!.days7, ), ), ), diff --git a/lib/widgets/option_box.dart b/lib/widgets/option_box.dart index 7c05a67..f19a88f 100644 --- a/lib/widgets/option_box.dart +++ b/lib/widgets/option_box.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; class OptionBox extends StatelessWidget { - final Widget child; final dynamic optionsValue; final dynamic itemValue; final void Function(dynamic) onTap; + final String label; const OptionBox({ Key? key, - required this.child, required this.optionsValue, required this.itemValue, required this.onTap, + required this.label, }) : super(key: key); @override @@ -25,19 +25,27 @@ class OptionBox extends StatelessWidget { child: AnimatedContainer( duration: const Duration(milliseconds: 250), curve: Curves.easeInOut, - padding: const EdgeInsets.all(15), + padding: const EdgeInsets.symmetric(vertical: 16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(50), - border: Border.all( - color: optionsValue == itemValue - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurfaceVariant - ), color: optionsValue == itemValue ? Theme.of(context).colorScheme.primary - : Colors.transparent, + : Theme.of(context).colorScheme.primaryContainer, + ), + child: AnimatedDefaultTextStyle( + duration: const Duration(milliseconds: 250), + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + color: optionsValue == itemValue + ? Theme.of(context).colorScheme.onInverseSurface + : Theme.of(context).colorScheme.onSurface + ), + child: Text( + label, + textAlign: TextAlign.center, + ), ), - child: child, ), ), ); From 151cbb996e96eb0a07cdf7d1ebfaa11836acb7ed Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 14:22:03 +0200 Subject: [PATCH 016/177] Bug fixes --- lib/models/dns_info.dart | 2 +- .../settings/dns/private_reverse_servers.dart | 2 +- lib/services/http_requests.dart | 250 +++++++++++------- 3 files changed, 153 insertions(+), 101 deletions(-) diff --git a/lib/models/dns_info.dart b/lib/models/dns_info.dart index 5ab3cc2..67103f7 100644 --- a/lib/models/dns_info.dart +++ b/lib/models/dns_info.dart @@ -13,7 +13,7 @@ class DnsInfo { int? cacheTtlMin; int? cacheTtlMax; bool? cacheOptimistic; - bool resolveClients; + bool? resolveClients; bool usePrivatePtrResolvers; List localPtrUpstreams; String blockingIpv4; diff --git a/lib/screens/settings/dns/private_reverse_servers.dart b/lib/screens/settings/dns/private_reverse_servers.dart index bf6f63b..cd02684 100644 --- a/lib/screens/settings/dns/private_reverse_servers.dart +++ b/lib/screens/settings/dns/private_reverse_servers.dart @@ -79,7 +79,7 @@ class _PrivateReverseDnsServersScreenState extends State res['statusCode'] ?? 'null').toString(), + resBody: result.map((res) => res['body'] ?? 'null').toString(), + ) + }; + } } else { return { @@ -636,22 +650,21 @@ class ApiClient { 'log': AppLog( type: 'get_clients', dateTime: DateTime.now(), - message: 'error_code_not_expected', + message: 'no_response', statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), resBody: result.map((res) => res['body'] ?? 'null').toString(), ) }; } - } - else { + } catch (e) { + Sentry.captureException(e); return { 'result': 'error', 'log': AppLog( type: 'get_clients', dateTime: DateTime.now(), message: 'no_response', - statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), - resBody: result.map((res) => res['body'] ?? 'null').toString(), + resBody: e.toString() ) }; } @@ -701,68 +714,94 @@ class ApiClient { String? responseStatus, String? search }) async { - final result = await apiRequest( - server: server, - method: 'get', - urlPath: '/querylog?limit=$count${offset != null ? '&offset=$offset' : ''}${olderThan != null ? '&older_than=${olderThan.toIso8601String()}' : ''}${responseStatus != null ? '&response_status=$responseStatus' : ''}${search != null ? '&search=$search' : ''}', - type: 'get_logs' - ); - - if (result['hasResponse'] == true) { - if (result['statusCode'] == 200) { - return { - 'result': 'success', - 'data': LogsData.fromJson(jsonDecode(result['body'])) - }; + try { + final result = await apiRequest( + server: server, + method: 'get', + urlPath: '/querylog?limit=$count${offset != null ? '&offset=$offset' : ''}${olderThan != null ? '&older_than=${olderThan.toIso8601String()}' : ''}${responseStatus != null ? '&response_status=$responseStatus' : ''}${search != null ? '&search=$search' : ''}', + type: 'get_logs' + ); + + if (result['hasResponse'] == true) { + if (result['statusCode'] == 200) { + return { + 'result': 'success', + 'data': LogsData.fromJson(jsonDecode(result['body'])) + }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'get_logs', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result['statusCode'].toString(), + resBody: result['body'] + ) + }; + } } else { - return { - 'result': 'error', - 'log': AppLog( - type: 'get_logs', - dateTime: DateTime.now(), - message: 'error_code_not_expected', - statusCode: result['statusCode'].toString(), - resBody: result['body'] - ) - }; + return result; } - } - else { - return result; + } catch (e) { + Sentry.captureException(e); + return { + 'result': 'error', + 'log': AppLog( + type: 'get_logs', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + resBody: e.toString() + ) + }; } } Future getFilteringRules() async { - final result = await apiRequest( - server: server, - method: 'get', - urlPath: '/filtering/status', - type: 'get_filtering_rules' - ); - - if (result['hasResponse'] == true) { - if (result['statusCode'] == 200) { - return { - 'result': 'success', - 'data': FilteringStatus.fromJson(jsonDecode(result['body'])) - }; + try { + final result = await apiRequest( + server: server, + method: 'get', + urlPath: '/filtering/status', + type: 'get_filtering_rules' + ); + + if (result['hasResponse'] == true) { + if (result['statusCode'] == 200) { + return { + 'result': 'success', + 'data': FilteringStatus.fromJson(jsonDecode(result['body'])) + }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'get_filtering_rules', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result['statusCode'].toString(), + resBody: result['body'] + ) + }; + } } else { - return { - 'result': 'error', - 'log': AppLog( - type: 'get_filtering_rules', - dateTime: DateTime.now(), - message: 'error_code_not_expected', - statusCode: result['statusCode'].toString(), - resBody: result['body'] - ) - }; + return result; } - } - else { - return result; + } catch (e) { + Sentry.captureException(e); + return { + 'result': 'error', + 'log': AppLog( + type: 'get_filtering_rules', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + resBody: e.toString() + ) + }; } } @@ -899,32 +938,46 @@ class ApiClient { } Future getFiltering() async { - final result = await Future.wait([ - apiRequest( - urlPath: '/filtering/status', - method: 'get', - server: server, - type: 'get_filtering_status' - ), - apiRequest( - urlPath: '/blocked_services/list', - method: 'get', - server: server, - type: 'get_filtering_status' - ), - ]); + try { + final result = await Future.wait([ + apiRequest( + urlPath: '/filtering/status', + method: 'get', + server: server, + type: 'get_filtering_status' + ), + apiRequest( + urlPath: '/blocked_services/list', + method: 'get', + server: server, + type: 'get_filtering_status' + ), + ]); - if (result[0]['hasResponse'] == true && result[0]['hasResponse'] == true) { - if (result[0]['statusCode'] == 200 && result[0]['statusCode'] == 200) { - return { - 'result': 'success', - 'data': Filtering.fromJson({ - ...jsonDecode(result[0]['body']), - "blocked_services": result[1]['body'] != null - ? jsonDecode(result[1]['body']) - : [] - }) - }; + if (result[0]['hasResponse'] == true && result[0]['hasResponse'] == true) { + if (result[0]['statusCode'] == 200 && result[0]['statusCode'] == 200) { + return { + 'result': 'success', + 'data': Filtering.fromJson({ + ...jsonDecode(result[0]['body']), + "blocked_services": result[1]['body'] != null + ? jsonDecode(result[1]['body']) + : [] + }) + }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'get_filtering_status', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), + resBody: result.map((res) => res['body'] ?? 'null').toString(), + ) + }; + } } else { return { @@ -932,22 +985,21 @@ class ApiClient { 'log': AppLog( type: 'get_filtering_status', dateTime: DateTime.now(), - message: 'error_code_not_expected', + message: 'no_response', statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), resBody: result.map((res) => res['body'] ?? 'null').toString(), ) }; } - } - else { + } catch (e) { + Sentry.captureException(e); return { 'result': 'error', 'log': AppLog( type: 'get_filtering_status', dateTime: DateTime.now(), message: 'no_response', - statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), - resBody: result.map((res) => res['body'] ?? 'null').toString(), + resBody: e.toString(), ) }; } From 48fc96a16a4d2544823b9acbb111776da9098027 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 14:37:18 +0200 Subject: [PATCH 017/177] Add lists fixes --- lib/providers/filtering_provider.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/providers/filtering_provider.dart b/lib/providers/filtering_provider.dart index 8b2d459..d416455 100644 --- a/lib/providers/filtering_provider.dart +++ b/lib/providers/filtering_provider.dart @@ -325,14 +325,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['result'] == 'error' && result1['log'].statusCode == '400' && result1['log'].resBody.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['result'] == 'error' && result1['log'].statusCode == '400' && result1['log'].resBody.toString().contains('url already exists')) { notifyListeners(); return { 'success': false, From ddd0fe177bc3931a3a8e7fa52e4f57c2298b02d6 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 14:52:08 +0200 Subject: [PATCH 018/177] Fixed update lists --- lib/l10n/app_en.arb | 1 + lib/l10n/app_es.arb | 1 + lib/screens/filters/list_details_screen.dart | 31 +++++++++++++------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 84a22f6..41fb9ba 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -317,6 +317,7 @@ "deletingRule": "Deleting rule...", "enablingList": "Enabling list...", "disablingList": "Disabling list...", + "savingList": "Saving list...", "disableFiltering": "Disable filtering", "enablingFiltering": "Enabling filtering...", "disablingFiltering": "Disabling filtering...", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 9b97a3a..2e0734c 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -317,6 +317,7 @@ "deletingRule": "Eliminando regla...", "enablingList": "Habilitando lista...", "disablingList": "Deshabilitando lista...", + "savingList": "Guardando lista...", "disableFiltering": "Deshabilitar filtrado", "enablingFiltering": "Habilitando filtrado...", "disablingFiltering": "Deshabilitando filtrado...", diff --git a/lib/screens/filters/list_details_screen.dart b/lib/screens/filters/list_details_screen.dart index 42464ec..e98c759 100644 --- a/lib/screens/filters/list_details_screen.dart +++ b/lib/screens/filters/list_details_screen.dart @@ -76,15 +76,20 @@ class _ListDetailsScreenState extends State { // ------- // } - void updateList(FilteringListActions action) async { + void updateList({ + required FilteringListActions action, + required Filter filterList, + }) async { ProcessModal processModal = ProcessModal(context: context); 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 ); @@ -204,7 +209,8 @@ class _ListDetailsScreenState extends State { list: list, type: widget.type, onEdit: ({required Filter list, required String type}) async => updateList( - FilteringListActions.edit + action: FilteringListActions.edit, + filterList: list ), dialog: true, ), @@ -217,7 +223,8 @@ class _ListDetailsScreenState extends State { list: list, type: widget.type, onEdit: ({required Filter list, required String type}) async => updateList( - FilteringListActions.edit + action: FilteringListActions.edit, + filterList: list ), dialog: false, ), @@ -302,9 +309,10 @@ class _ListDetailsScreenState extends State { children: [ IconButton( onPressed: () => updateList( - list!.enabled == true + action: list!.enabled == true ? FilteringListActions.disable - : FilteringListActions.enable + : FilteringListActions.enable, + filterList: list ), icon: Icon( list.enabled == true @@ -371,9 +379,10 @@ class _ListDetailsScreenState extends State { right: 20, child: FloatingActionButton( onPressed: () => updateList( - list!.enabled == true + action: list!.enabled == true ? FilteringListActions.disable - : FilteringListActions.enable + : FilteringListActions.enable, + filterList: list ), child: Icon( list.enabled == true From f84b217d919e07e50e575bb2d64b4580fc8d50e7 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 21:46:18 +0200 Subject: [PATCH 019/177] Updated dhcp interfaces list --- lib/screens/settings/dhcp/dhcp.dart | 4 +- .../settings/dhcp/dhcp_interface_item.dart | 136 ++++++++ .../settings/dhcp/select_interface_modal.dart | 316 +++++++----------- 3 files changed, 266 insertions(+), 190 deletions(-) create mode 100644 lib/screens/settings/dhcp/dhcp_interface_item.dart diff --git a/lib/screens/settings/dhcp/dhcp.dart b/lib/screens/settings/dhcp/dhcp.dart index a93a338..724a33a 100644 --- a/lib/screens/settings/dhcp/dhcp.dart +++ b/lib/screens/settings/dhcp/dhcp.dart @@ -345,7 +345,9 @@ class _DhcpScreenState extends State { }), dialog: false, ), - isScrollControlled: true + isScrollControlled: true, + useSafeArea: true, + backgroundColor: Colors.transparent ); } }); diff --git a/lib/screens/settings/dhcp/dhcp_interface_item.dart b/lib/screens/settings/dhcp/dhcp_interface_item.dart new file mode 100644 index 0000000..900328e --- /dev/null +++ b/lib/screens/settings/dhcp/dhcp_interface_item.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/models/dhcp.dart'; + +class DhcpInterfaceItem extends StatelessWidget { + final NetworkInterface networkInterface; + final void Function(NetworkInterface) onSelect; + + const DhcpInterfaceItem({ + Key? key, + required this.networkInterface, + required this.onSelect + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Navigator.pop(context); + onSelect(networkInterface); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + networkInterface.name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurface + ), + ), + Row( + children: [ + Text( + "${AppLocalizations.of(context)!.hardwareAddress}: ", + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + Text( + networkInterface.hardwareAddress, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ], + ), + const SizedBox(height: 5), + if (networkInterface.flags.isNotEmpty) ...[ + Row( + children: [ + Text( + "Flags: ", + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + Text( + networkInterface.flags.join(', '), + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ], + ), + const SizedBox(height: 5), + ], + if (networkInterface.gatewayIp != null && networkInterface.gatewayIp != '') ...[ + Row( + children: [ + Text( + "${AppLocalizations.of(context)!.gatewayIp}: ", + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + Text( + networkInterface.gatewayIp!, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ], + ), + const SizedBox(height: 5), + ], + if (networkInterface.ipv4Addresses.isNotEmpty) ...[ + Row( + children: [ + Flexible( + child: Text( + "${AppLocalizations.of(context)!.ipv4addresses}: ${networkInterface.ipv4Addresses.join(', ')}", + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ) + ], + ), + const SizedBox(height: 5), + ], + if (networkInterface.ipv6Addresses.isNotEmpty) ...[ + Row( + children: [ + Flexible( + child: Text( + "${AppLocalizations.of(context)!.ipv6addresses}: ${networkInterface.ipv6Addresses.join(', ')}", + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ) + ], + ), + ] + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/dhcp/select_interface_modal.dart b/lib/screens/settings/dhcp/select_interface_modal.dart index 01b7e06..668548d 100644 --- a/lib/screens/settings/dhcp/select_interface_modal.dart +++ b/lib/screens/settings/dhcp/select_interface_modal.dart @@ -3,6 +3,8 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_interface_item.dart'; + import 'package:adguard_home_manager/models/dhcp.dart'; class SelectInterfaceModal extends StatelessWidget { @@ -19,205 +21,141 @@ class SelectInterfaceModal extends StatelessWidget { @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( - Icons.settings_ethernet_rounded, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.selectInterface, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ), - ], - ), - const SizedBox(height: 16), - ListView.builder( - primary: false, - shrinkWrap: true, - itemCount: interfaces.length, - itemBuilder: (context, index) => Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - Navigator.pop(context); - onSelect(interfaces[index]); - }, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - interfaces[index].name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Row( - children: [ - Text( - "${AppLocalizations.of(context)!.hardwareAddress}: ", - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - Text( - interfaces[index].hardwareAddress, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ], - ), - const SizedBox(height: 5), - if (interfaces[index].flags.isNotEmpty) ...[ - Row( - children: [ - Text( - "Flags: ", - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - Text( - interfaces[index].flags.join(', '), - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ], - ), - const SizedBox(height: 5), - ], - if (interfaces[index].gatewayIp != null && interfaces[index].gatewayIp != '') ...[ - Row( - children: [ - Text( - "${AppLocalizations.of(context)!.gatewayIp}: ", - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - Text( - interfaces[index].gatewayIp!, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ], - ), - const SizedBox(height: 5), - ], - if (interfaces[index].ipv4Addresses.isNotEmpty) ...[ - Row( - children: [ - Flexible( - child: Text( - "${AppLocalizations.of(context)!.ipv4addresses}: ${interfaces[index].ipv4Addresses.join(', ')}", - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ) - ], - ), - const SizedBox(height: 5), - ], - if (interfaces[index].ipv6Addresses.isNotEmpty) ...[ - Row( - children: [ - Flexible( - child: Text( - "${AppLocalizations.of(context)!.ipv6addresses}: ${interfaces[index].ipv6Addresses.join(', ')}", - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ) - ], - ), - ] - ], - ), - ), - ), - ) - ), - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.all(20), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) - ) - ], - ), - ), - if (Platform.isIOS) const SizedBox(height: 16) - ], - ); - } - if (dialog == true) { return Dialog( child: ConstrainedBox( constraints: const BoxConstraints( - maxWidth: 500 + maxWidth: 500, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Wrap( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + Icon( + Icons.settings_ethernet_rounded, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.selectInterface, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ], + ), + const SizedBox(height: 16), + ], + ), + ), + Expanded( + child: ListView.builder( + itemCount: interfaces.length, + itemBuilder: (context, index) => DhcpInterfaceItem( + networkInterface: interfaces[index], + onSelect: onSelect + ) + ), + ), + Padding( + padding: const EdgeInsets.all(20), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ) + ], + ), + ), + if (Platform.isIOS) const SizedBox(height: 16) + ], ), - child: content() ), ); } else { - return Container( - decoration: BoxDecoration( - color: Theme.of(context).dialogBackgroundColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28) - ) + return GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Container( + color: Colors.transparent, + child: DraggableScrollableSheet( + initialChildSize: 0.6, + minChildSize: 0.3, + maxChildSize: 1, + builder: (context, controller) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28), + ), + ), + child: Column( + children: [ + Container( + margin: const EdgeInsets.all(16), + width: 36, + height: 4, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.grey + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + children: [ + Icon( + Icons.settings_ethernet_rounded, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.selectInterface, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ), + Expanded( + child: ListView.builder( + controller: controller, + itemCount: interfaces.length, + itemBuilder: (context, index) => DhcpInterfaceItem( + networkInterface: interfaces[index], + onSelect: onSelect + ) + ) + ), + const SizedBox(height: 16) + ], + ), + ); + }, ), - child: content() - ); + ), + ); } } } \ No newline at end of file From 4b03dbeb18819fc19b4f023d3245218208ec628f Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 11 Oct 2023 22:11:08 +0200 Subject: [PATCH 020/177] Fixed issue edit client --- lib/screens/clients/added_list.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index 8f27491..24b69ae 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -131,6 +131,7 @@ class _AddedListState extends State { openClientFormModal( context: context, width: width, + client: client, onConfirm: confirmEditClient, onDelete: deleteClient ); From 9e57b57ddf64820a83c3f587fbfcffa267db0ded Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 11 Oct 2023 22:14:32 +0200 Subject: [PATCH 021/177] Added loading indicator edit connection --- lib/widgets/add_server/add_server_modal.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/widgets/add_server/add_server_modal.dart b/lib/widgets/add_server/add_server_modal.dart index 294cd7d..c587c7e 100644 --- a/lib/widgets/add_server/add_server_modal.dart +++ b/lib/widgets/add_server/add_server_modal.dart @@ -126,6 +126,8 @@ class _AddServerModalState extends State { } void connect() async { + setState(() => isConnecting = true); + Server serverObj = Server( id: uuid.v4(), name: nameController.text, @@ -140,7 +142,6 @@ class _AddServerModalState extends State { : null, runningOnHa: homeAssistant ); - setState(() => isConnecting = true); final result = homeAssistant == true ? await loginHA(serverObj) @@ -211,10 +212,11 @@ class _AddServerModalState extends State { else { Navigator.pop(context); } - return; } void edit() async { + setState(() => isConnecting = true); + final Server serverObj = Server( id: widget.server!.id, name: nameController.text, @@ -286,7 +288,6 @@ class _AddServerModalState extends State { else { Navigator.pop(context); } - return; } Widget actions() { From 077727c1be8ba372083b92ef2b25415744e5e9fd Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 15 Oct 2023 23:47:42 +0200 Subject: [PATCH 022/177] Fixed spelling issue --- .gitignore | 4 +++- l10n.yaml | 3 ++- lib/l10n/app_en.arb | 4 ++-- lib/l10n/app_es.arb | 4 ++-- lib/l10n/app_pl.arb | 4 ++-- lib/l10n/app_zh.arb | 4 ++-- lib/l10n/app_zh_CN.arb | 4 ++-- lib/screens/home/combined_chart.dart | 4 ++-- lib/screens/home/home.dart | 2 +- 9 files changed, 18 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 429a1bd..6f4a17f 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,6 @@ app.*.map.json /android/app/profile /android/app/release -/debian/packages \ No newline at end of file +/debian/packages + +untranslated.json \ No newline at end of file diff --git a/l10n.yaml b/l10n.yaml index 4e6692e..764d005 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,3 +1,4 @@ arb-dir: lib/l10n template-arb-file: app_en.arb -output-localization-file: app_localizations.dart \ No newline at end of file +output-localization-file: app_localizations.dart +untranslated-messages-file: untranslated.json \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 41fb9ba..f54e491 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -171,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", @@ -651,7 +651,7 @@ "october": "October", "november": "November", "december": "December", - "malwarePhising": "Malware/phising", + "malwarePhishing": "Malware/phishing", "queries": "Queries", "adultSites": "Adult sites", "quickFilters": "Quick filters", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 2e0734c..d0c7a15 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -171,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", @@ -651,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", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index a3dabaa..077fcbb 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -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", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 796a6c6..8d246eb 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -170,7 +170,7 @@ "dnsQueries": "DNS 查询", "average": "平均值", "blockedFilters": "被过滤器拦截", - "malwarePhisingBlocked": "被拦截的恶意/钓鱼网站", + "malwarePhishingBlocked": "被拦截的恶意/钓鱼网站", "blockedAdultWebsites": "被拦截的成人网站", "generalSettings": "常规设置", "generalSettingsDescription": "各种不同的设置", @@ -649,7 +649,7 @@ "october": "10月", "november": "11月", "december": "12月", - "malwarePhising": "恶意/钓鱼网站", + "malwarePhishing": "恶意/钓鱼网站", "queries": "查询", "adultSites": "成人网站", "quickFilters": "状态过滤器", diff --git a/lib/l10n/app_zh_CN.arb b/lib/l10n/app_zh_CN.arb index 796a6c6..8d246eb 100644 --- a/lib/l10n/app_zh_CN.arb +++ b/lib/l10n/app_zh_CN.arb @@ -170,7 +170,7 @@ "dnsQueries": "DNS 查询", "average": "平均值", "blockedFilters": "被过滤器拦截", - "malwarePhisingBlocked": "被拦截的恶意/钓鱼网站", + "malwarePhishingBlocked": "被拦截的恶意/钓鱼网站", "blockedAdultWebsites": "被拦截的成人网站", "generalSettings": "常规设置", "generalSettingsDescription": "各种不同的设置", @@ -649,7 +649,7 @@ "october": "10月", "november": "11月", "december": "12月", - "malwarePhising": "恶意/钓鱼网站", + "malwarePhishing": "恶意/钓鱼网站", "queries": "查询", "adultSites": "成人网站", "quickFilters": "状态过滤器", diff --git a/lib/screens/home/combined_chart.dart b/lib/screens/home/combined_chart.dart index d7328d1..f781b7d 100644 --- a/lib/screens/home/combined_chart.dart +++ b/lib/screens/home/combined_chart.dart @@ -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 ) , diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index 7acfa63..01d251d 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -149,7 +149,7 @@ class _HomeState extends State { widthFactor: width > 700 ? 0.5 : 1, child: HomeChart( data: statusProvider.serverStatus!.stats.replacedSafebrowsing, - label: AppLocalizations.of(context)!.malwarePhisingBlocked, + 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, From 3ff179d24b4d772c34a6060f1c14ec2f8f7e0166 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 21 Oct 2023 18:54:40 +0200 Subject: [PATCH 023/177] Added turkish translation --- lib/l10n/app_tr.arb | 666 ++++++++++++++++++++++++++++++++++++++++++++ lib/main.dart | 3 +- 2 files changed, 668 insertions(+), 1 deletion(-) create mode 100644 lib/l10n/app_tr.arb diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb new file mode 100644 index 0000000..b7b3c93 --- /dev/null +++ b/lib/l10n/app_tr.arb @@ -0,0 +1,666 @@ +{ + "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 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 bileşeni", + "safeBrowsingWidget": "Güvenli gezinti bileşeni", + "parentalFilteringWidget": "Ebeveyn filtreleme bileşeni", + "safeSearchWidget": "Güvenli arama bileşeni", + "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 sorgulanan alan adları", + "viewMore": "Daha fazla göster", + "topClients": "Öne çıkan istemciler", + "topBlockedDomains": "En çok engellenen alan adları", + "appSettings": "Uygulama ayarları", + "theme": "Tema", + "light": "Aydınlık", + "dark": "Karanlık", + "systemDefined": "Tanımlanmış sistem", + "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": "Tarafından oluşturuldu", + "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ı kontrol etme", + "dontCheckCertificateDescription": "Sunucunun SSL sertifikası doğrulamasını geçersiz kılar", + "advancedSetupDescription": "Gelişmiş seçenekler", + "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": "Tür", + "clas": "Sınıf", + "response": "Yanıt", + "dnsServer": "DNS sunucusu", + "elapsedTime": "Zaman aşımı", + "responseCode": "Yanıt kodu", + "client": "İstemci", + "deviceIp": "IP adresi", + "deviceName": "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 zamanda kaydedilmiş herhangi bir kayıt olmayabilir. 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": "Filtreler tarafından engellenen", + "malwarePhisingBlocked": "Engellenen zararlı yazılım/oltalama ", + "blockedAdultWebsites": "Engellenen yetişkin siteleri", + "generalSettings": "Genel ayarlar", + "generalSettingsDescription": "Çeşitli farklı ayarlar", + "hideZeroValues": "Sıfır değerlerini gizle", + "hideZeroValuesDescription": "Ana ekranda, değeri sıfır olan blokları gizle", + "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ştirin", + "enableParentalControl": "Ebeveyn kontrolünü etkinleştirinl", + "enableSafeSearch": "Güvenli aramayı etkinleştirin", + "blockedServices": "Engellenen hizmetler", + "selectBlockedServices": "Engellenen hizmetleri seç", + "noBlockedServicesSelected": "Engellenen hizmetler seçilmedi", + "services": "Hizmetler", + "servicesBlocked": "Hizmetler engellendi", + "tagsSelected": "Seçilen etiketler", + "upstreamServers": "Üst akış sunucuları", + "serverAddress": "Sunucu adresi", + "noUpstreamServers": "Üst akış sunucusu yok.", + "willBeUsedGeneralServers": "Genel üst akış 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 kural", + "enabledRules": "Etkin kural", + "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": "URL geçerli değil", + "urlAbsolutePath": "URL veya kesin dosya yolu", + "addingList": "Liste ekleniyor...", + "listAdded": "Liste başarıyla eklendi. Eklenen öğeler:", + "listAlreadyAdded": "Liste zaten eklenmiş", + "listUrlInvalid": "Liste URL'si 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 durumu", + "loadingServerInfo": "Sunucu bilgisi yükleniyor...", + "serverInfoNotLoaded": "Sunucu bilgisi yüklenemedi.", + "dnsAddresses": "DNS adresleri", + "seeDnsAddresses": "DNS adreslerine bakın", + "dnsPort": "DNS bağlantı noktası", + "httpPort": "HTTP bağlantı noktası", + "protectionEnabled": "Koruma etkin", + "dhcpAvailable": "DHCP mevcut", + "serverRunning": "Sunucu çalışıyor", + "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 verilen istemci yok", + "allowedClientsDescription": "Eğer bu liste girişlere sahipse, AdGuard Home sadece bu istemcilerden gelen istekleri kabul edecek.", + "blockedClientsDescription": "Eğer bu liste girişlere sahipse, AdGuard Home bu istemcilerden gelen istekleri reddedecektir. Bu alan, İzin Verilen İstemciler bölümünde girişler varsa dikkate alınmaz.", + "disallowedDomainsDescription": "AdGuard Home, bu alanlara 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": "Ana bilgisayarı 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": "Ana bilgisayar kontrol ediliyor", + "errorCheckingHost": "Ana bilgisayar kontrol edilemedi", + "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 alanlarına erişimi engeller.", + "example2": "example.org ve tüm alt alanları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": "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 yazma kuralları yükleniyor...", + "rewriteRulesNotLoaded": "DNS yeniden yazma kuralları yüklenemedi.", + "noRewriteRules": "DNS yeniden yazma kuralları yok", + "answer": "Cevap", + "deleteDnsRewrite": "DNS yeniden yazmayı sil", + "deleteDnsRewriteMessage": "Bu DNS yeniden yazmasını silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "dnsRewriteRuleDeleted": "DNS yeniden yazma kuralı başarıyla silindi", + "dnsRewriteRuleNotDeleted": "DNS yeniden yazma kuralı silinemedi", + "addDnsRewrite": "DNS yeniden yazmayı ekle", + "addingRewrite": "Yeniden yazma ekleniyor...", + "dnsRewriteRuleAdded": "DNS yeniden yazma kuralı başarıyla eklendi", + "dnsRewriteRuleNotAdded": "DNS yeniden yazma kuralı eklenemedi", + "logsSettings": "Günlük ayarları", + "enableLog": "Günlüğü etkinleştir", + "clearLogs": "Günlükleri temizle", + "anonymizeClientIp": "İstemci IP'sini anonimleştirin", + "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ışıyor", + "serverError": "Sunucu hatası", + "noItems": "Burada gösterilecek öğe yok", + "dnsSettings": "DNS ayarları", + "dnsSettingsDescription": "DNS sunucuları ile bağlantıyı yapılandır", + "upstreamDns": "Üst akış DNS sunucuları", + "bootstrapDns": "Önyükleme DNS sunucuları", + "noUpstreamDns": "Üst akış 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 akış sunucusuna sorgu yap. AdGuard Home, en hızlı sunucunun daha sık kullanılması için ağırlıklı rasgele bir algoritma kullanır.", + "parallelRequestsDescription": "Çözümlemeyi hızlandırmak için tüm üst akış sunucularına aynı anda sorgu yaparak 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ı, yukarı akışlarda 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 kullanma", + "usePrivateReverseDnsResolversDescription": "Bu üst akış 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ştirin", + "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 yukarı akış sunucuları) istemcilerin ana bilgisayar adlarını tersine çöz.", + "dnsServerSettings": "AdGuard Home DNS sunucusu ayarları", + "limitRequestsSecond": "Saniye başına hız limiti", + "valueNotNumber": "Değer bir rakam değildir", + "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üğünde kaydet.", + "enableDnssec": "DNSSEC'i Etkinleştir", + "enableDnssecDescription": "Giden DNS sorgularında DNSSEC bayrağını ayarla ve sonucu kontrol et (DNSSEC etkinleştirilmiş bir çözümleyici gereklidir).", + "disableResolvingIpv6": "IPv6 adreslerinin çözümlenmesini devre dışı bırak", + "disableResolvingIpv6Description": "IPv6 adresleri için tüm DNS sorgularını devre dışı bırakın (AAAA yazı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 kullanım süresi değerini uzatı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 akış 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 cevap verecektir.", + "serverConfiguration": "Sunucu yapılandırması", + "domainName": "Alan adı", + "domainNameDescription": "Eğer ayarlanırsa, AdGuard Home İstemci 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ı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": "Bir sertifika dosyası yolu ayarlayın", + "pasteCertificateContent": "Sertifika içeriğini yapıştır", + "certificatePath": "Sertifika dosya yolu", + "certificateContent": "Sertifika içeriği", + "privateKey": "Private key", + "privateKeyFile": "Özel bir anahtar dosyası belirle", + "pastePrivateKey": "Özel anahtar içeriğini yapıştır", + "usePreviousKey": "Önceden kaydedilmiş anahtarı kullan", + "privateKeyPath": "Özel anahtar 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ın", + "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": "İhraççı", + "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 zaman", + "timeLogsDescription": "Günlükler listesinde 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": "Cevaplar", + "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çilen istemciler", + "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} dakikalar", + "hour": "{time} saat", + "hours": "{time} saatler", + "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 URL'sini kopyala", + "listUrlCopied": "Panoya kopyalanan URL'yi 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": "IP günlüklerde", + "ipLogsDescription": "Günlüklerde istemci adı yerine her zaman IP adresini göster", + "application": "Uygulama", + "combinedChart": "Birleştirilmiş grafik", + "combinedChartDescription": "Tüm grafikleri bir araya getirin", + "statistics": "İstatistikler", + "errorLoadFilters": "Filtreler yüklenirken hata oluştu.", + "clientRemovedSuccessfully": "İstemci başarıyla kaldırıldı.", + "editRewriteRule": "Yeniden yazma kuralını düzenle", + "dnsRewriteRuleUpdated": "DNS yeniden yazma kuralı başarıyla güncellendi", + "dnsRewriteRuleNotUpdated": "DNS yeniden yazma 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", + "malwarePhising": "Zararlı yazılım/oltalama", + "queries": "Sorgular", + "adultSites": "Yetişkin siteleri", + "quickFilters": "Hızlı filtreler", + "searchDomainInternet": "İnternette alan adı ara", + "hideServerAddress": "Sunucu adresini gizle", + "hideServerAddressDescription": "Ana ekranda sunucu adresini gizler", + "topItemsOrder": "En iyi ürün siparişi", + "topItemsOrderDescription": "Ana ekran üst öğe listelerini sırala", + "topItemsReorderInfo": "Yeniden sıralamak için bir öğeyi basılı tutun ve kaydırın.", + "discardChanges": "Değişiklikleri iptal edin", + "discardChangesDescription": "Değişiklikleri iptal etmek istediğinizden emin misiniz?" +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index a894dc6..98bb878 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -229,7 +229,8 @@ class _MainState extends State
{ Locale('es', ''), Locale('zh', ''), Locale('zh', 'CN'), - Locale('pl', '') + Locale('pl', ''), + Locale('tr', '') ], scaffoldMessengerKey: scaffoldMessengerKey, builder: (context, child) { From f161842a0857918b08af351cba77bd1dcc01fb9b Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 21 Oct 2023 19:02:52 +0200 Subject: [PATCH 024/177] Fixed issue private dns resolvers list --- .../settings/dns/private_reverse_servers.dart | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/screens/settings/dns/private_reverse_servers.dart b/lib/screens/settings/dns/private_reverse_servers.dart index cd02684..ea16ea3 100644 --- a/lib/screens/settings/dns/private_reverse_servers.dart +++ b/lib/screens/settings/dns/private_reverse_servers.dart @@ -21,12 +21,7 @@ class PrivateReverseDnsServersScreen extends StatefulWidget { class _PrivateReverseDnsServersScreenState extends State { List defaultReverseResolvers = []; bool editReverseResolvers = false; - List> reverseResolversControllers = [ - { - 'controller': TextEditingController(), - 'error': null - } - ]; + List> reverseResolversControllers = []; bool usePrivateReverseDnsResolvers = false; bool enableReverseResolve = false; @@ -67,13 +62,19 @@ class _PrivateReverseDnsServersScreenState extends State Padding( - padding: const EdgeInsets.only( + padding: const EdgeInsets.only( left: 16, right: 6, bottom: 20 ), child: Row( From 7cd3744f70def2cb3a6feaae1e4716ebad236dfa Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 21 Oct 2023 19:04:58 +0200 Subject: [PATCH 025/177] Bug fix --- lib/models/dns_info.dart | 2 +- lib/screens/settings/dns/private_reverse_servers.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/models/dns_info.dart b/lib/models/dns_info.dart index 67103f7..4e85400 100644 --- a/lib/models/dns_info.dart +++ b/lib/models/dns_info.dart @@ -14,7 +14,7 @@ class DnsInfo { int? cacheTtlMax; bool? cacheOptimistic; bool? resolveClients; - bool usePrivatePtrResolvers; + bool? usePrivatePtrResolvers; List localPtrUpstreams; String blockingIpv4; String blockingIpv6; diff --git a/lib/screens/settings/dns/private_reverse_servers.dart b/lib/screens/settings/dns/private_reverse_servers.dart index ea16ea3..eb0f16a 100644 --- a/lib/screens/settings/dns/private_reverse_servers.dart +++ b/lib/screens/settings/dns/private_reverse_servers.dart @@ -79,7 +79,7 @@ class _PrivateReverseDnsServersScreenState extends State Date: Sat, 21 Oct 2023 21:06:38 +0200 Subject: [PATCH 026/177] Added ring chart home top items --- lib/l10n/app_en.arb | 11 +- lib/l10n/app_es.arb | 11 +- lib/screens/home/home.dart | 229 ++++++++------- lib/screens/home/top_items.dart | 451 ++++++++++++++++++++++-------- lib/widgets/custom_pie_chart.dart | 32 +++ pubspec.lock | 8 + pubspec.yaml | 1 + 7 files changed, 512 insertions(+), 231 deletions(-) create mode 100644 lib/widgets/custom_pie_chart.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index f54e491..b9c7b1e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -57,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": "Cients", + "topBlockedDomains": "Blocked domains", "appSettings": "App settings", "theme": "Theme", "light": "Light", @@ -662,5 +662,8 @@ "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" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index d0c7a15..fe13bcd 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -57,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", @@ -662,5 +662,8 @@ "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" } \ No newline at end of file diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index 01d251d..59fb0bf 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -175,110 +175,66 @@ class _HomeState extends State { 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', - ), - ), - ); + TopItemsLists(order: appConfigProvider.homeTopItemsOrder), - 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', - ), - ), - ); + // 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.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', - ), - ), - ); + // 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(), - ), - ], - ) + // default: + // return const SizedBox(); + // } + // }).toList(), + // ), + // ], + // ) ]; } @@ -348,4 +304,75 @@ class _HomeState extends State { ), ); } +} + +class TopItemsLists extends StatelessWidget { + final List order; + + const TopItemsLists({ + Key? key, + required this.order, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final statusProvider = Provider.of(context); + + List bottom = [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Divider( + thickness: 1, + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.2), + ), + ), + const SizedBox(height: 16), + ]; + + return Column( + children: order.asMap().entries.map((item) { + switch (item.value) { + case HomeTopItems.queriedDomains: + return Column( + children: [ + TopItems( + label: AppLocalizations.of(context)!.topQueriedDomains, + data: statusProvider.serverStatus!.stats.topQueriedDomains, + type: 'topQueriedDomains', + ), + if (item.key < order.length - 1) ...bottom + ], + ); + + case HomeTopItems.blockedDomains: + return Column( + children: [ + TopItems( + label: AppLocalizations.of(context)!.topBlockedDomains, + data: statusProvider.serverStatus!.stats.topBlockedDomains, + type: 'topBlockedDomains', + ), + if (item.key < order.length - 1) ...bottom + ], + ); + + case HomeTopItems.recurrentClients: + return Column( + children: [ + TopItems( + label: AppLocalizations.of(context)!.topClients, + data: statusProvider.serverStatus!.stats.topClients, + type: 'topClients', + clients: true, + ), + if (item.key < order.length - 1) ...bottom + ], + ); + + default: + return const SizedBox(); + } + }).toList(), + ); + } } \ No newline at end of file diff --git a/lib/screens/home/top_items.dart b/lib/screens/home/top_items.dart index c43ba3a..9cacd68 100644 --- a/lib/screens/home/top_items.dart +++ b/lib/screens/home/top_items.dart @@ -6,6 +6,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/widgets/custom_pie_chart.dart'; import 'package:adguard_home_manager/widgets/domain_options.dart'; import 'package:adguard_home_manager/screens/top_items/top_items_modal.dart'; import 'package:adguard_home_manager/screens/top_items/top_items.dart'; @@ -15,7 +16,7 @@ import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; -class TopItems extends StatelessWidget { +class TopItems extends StatefulWidget { final String type; final String label; final List> data; @@ -29,104 +30,30 @@ class TopItems extends StatelessWidget { this.clients }) : super(key: key); + @override + State createState() => _TopItemsState(); +} + +class _TopItemsState extends State { + bool _showChart = true; + + final colors = [ + Colors.red, + Colors.green, + Colors.blue, + Colors.orange, + Colors.teal, + Colors.grey + ]; + @override Widget build(BuildContext context) { final statusProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - final logsProvider = Provider.of(context); final width = MediaQuery.of(context).size.width; - Widget rowItem(Map item) { - String? name; - if (clients != null && clients == true) { - try { - name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(item.keys.toList()[0])).name; - } catch (e) { - // ---- // - } - } - - return Material( - color: Colors.transparent, - child: DomainOptions( - item: item.keys.toList()[0], - isClient: type == 'topClients', - isBlocked: type == 'topBlockedDomains', - onTap: () { - if (type == 'topQueriedDomains' || type == 'topBlockedDomains') { - logsProvider.setSearchText(item.keys.toList()[0]); - logsProvider.setSelectedClients(null); - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: 'all', - searchText: item.keys.toList()[0], - clients: null - ) - ); - appConfigProvider.setSelectedScreen(2); - } - else if (type == 'topClients') { - logsProvider.setSearchText(null); - logsProvider.setSelectedClients([item.keys.toList()[0]]); - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: 'all', - searchText: null, - clients: [item.keys.toList()[0]] - ) - ); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 8 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - item.keys.toList()[0], - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - if (name != null) ...[ - const SizedBox(height: 5), - Text( - name, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ] - ], - ), - ), - Text( - item.values.toList()[0].toString(), - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ) - ], - ), - ), - ), - ); - } - List> generateData() { - switch (type) { + switch (widget.type) { case 'topQueriedDomains': return statusProvider.serverStatus!.stats.topQueriedDomains; @@ -141,37 +68,123 @@ class TopItems extends StatelessWidget { } } + Map chartData() { + Map values = {}; + widget.data.sublist(0, widget.data.length > 5 ? 5 : widget.data.length).forEach((element) { + values = { + ...values, + element.keys.first: element.values.first.toDouble() + }; + }); + if (widget.data.length > 5) { + final int rest = List.from( + widget.data.sublist(5, widget.data.length).map((e) => e.values.first.toInt()) + ).reduce((a, b) => a + b); + values = { + ...values, + AppLocalizations.of(context)!.others: rest.toDouble() + }; + } + return values; + } + + final List itemsList = widget.data.sublist( + 0, + widget.data.length > 5 ? 5 : widget.data.length + ).asMap().entries.map((e) => RowItem( + clients: widget.clients ?? false, + domain: e.value.keys.toList()[0], + number: e.value.values.toList()[0].toString(), + type: widget.type, + chartColor: _showChart ? colors[e.key] : null, + )).toList(); + + final Widget noItems = Padding( + padding: const EdgeInsets.only( + bottom: 20, + top: 10 + ), + child: Text( + AppLocalizations.of(context)!.noItems, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ); + + final Widget chart = CustomPieChart( + data: chartData(), + colors: colors + ); + return SizedBox( child: Column( children: [ - Text( - label, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: width <= 700 + ? MainAxisAlignment.spaceBetween + : MainAxisAlignment.center, + children: [ + Text( + widget.label, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + if (width <= 700) TextButton( + onPressed: () => setState(() => _showChart = !_showChart), + child: Text( + _showChart + ? AppLocalizations.of(context)!.hideChart + : AppLocalizations.of(context)!.showChart + ) + ) + ], ), ), - const SizedBox(height: 20), - if (data.isEmpty) Padding( - padding: const EdgeInsets.only( - bottom: 20, - top: 10 - ), - child: Text( - AppLocalizations.of(context)!.noItems, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), + + const SizedBox(height: 24), + + if (widget.data.isEmpty) noItems, + if (widget.data.isNotEmpty && width > 700) SizedBox( + height: 240, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + flex: 1, + child: Padding( + padding: const EdgeInsets.all(16), + child: chart, + ) + ), + Expanded( + flex: 2, + child: Column( + children: [ + ...itemsList, + OthersRowItem(items: widget.data) + ] + ), + ) + ], ), ), - if (data.isNotEmpty) rowItem(data[0]), - if (data.length >= 2) rowItem(data[1]), - if (data.length >= 3) rowItem(data[2]), - if (data.length >= 4) rowItem(data[3]), - if (data.length >= 5) rowItem(data[4]), - if (data.length > 5) ...[ + if (widget.data.isNotEmpty && width <= 700) ...[ + if (_showChart) ...[ + chart, + const SizedBox(height: 16), + ], + ...itemsList, + if (_showChart) OthersRowItem(items: widget.data) + ], + + if (widget.data.length > 5) ...[ const SizedBox(height: 20), Padding( padding: const EdgeInsets.only(right: 20), @@ -185,9 +198,9 @@ class TopItems extends StatelessWidget { context: context, barrierDismissible: false, builder: (context) => TopItemsModal( - type: type, - title: label, - isClient: clients, + type: widget.type, + title: widget.label, + isClient: widget.clients, data: generateData(), ) ) @@ -196,9 +209,9 @@ class TopItems extends StatelessWidget { Navigator.of(context).push( MaterialPageRoute( builder: (context) => TopItemsScreen( - type: type, - title: label, - isClient: clients, + type: widget.type, + title: widget.label, + isClient: widget.clients, data: generateData(), ) ) @@ -226,4 +239,198 @@ class TopItems extends StatelessWidget { ), ); } +} + +class RowItem extends StatelessWidget { + final String type; + final Color? chartColor; + final String domain; + final String number; + final bool clients; + + const RowItem({ + Key? key, + required this.type, + this.chartColor, + required this.domain, + required this.number, + required this.clients + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final statusProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); + final logsProvider = Provider.of(context); + + String? name; + if (clients == true) { + try { + name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(domain)).name; + } catch (e) { + // ---- // + } + } + + return Material( + color: Colors.transparent, + child: DomainOptions( + item: domain, + isClient: type == 'topClients', + isBlocked: type == 'topBlockedDomains', + onTap: () { + if (type == 'topQueriedDomains' || type == 'topBlockedDomains') { + logsProvider.setSearchText(domain); + logsProvider.setSelectedClients(null); + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: 'all', + searchText: domain, + clients: null + ) + ); + appConfigProvider.setSelectedScreen(2); + } + else if (type == 'topClients') { + logsProvider.setSearchText(null); + logsProvider.setSelectedClients([domain]); + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: 'all', + searchText: null, + clients: [domain] + ) + ); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 8 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Row( + children: [ + if (chartColor != null) Container( + margin: const EdgeInsets.only(right: 16), + width: 12, + height: 12, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: chartColor + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + domain, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + if (name != null) ...[ + const SizedBox(height: 5), + Text( + name, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ] + ], + ), + ), + ], + ), + ), + const SizedBox(width: 16), + Text( + number, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + ), + ), + ); + } +} + +class OthersRowItem extends StatelessWidget { + final List> items; + + const OthersRowItem({ + Key? key, + required this.items + }) : super(key: key); + + @override + Widget build(BuildContext context) { + + if (items.length <= 5) { + return const SizedBox(); + } + + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 8 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Row( + children: [ + Container( + margin: const EdgeInsets.only(right: 16), + width: 12, + height: 12, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: Colors.grey + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.others, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ), + ], + ), + ), + const SizedBox(width: 16), + Text( + List.from( + items.sublist(5, items.length).map((e) => e.values.first.toInt()) + ).reduce((a, b) => a + b).toString(), + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + ); + } } \ No newline at end of file diff --git a/lib/widgets/custom_pie_chart.dart b/lib/widgets/custom_pie_chart.dart new file mode 100644 index 0000000..2968568 --- /dev/null +++ b/lib/widgets/custom_pie_chart.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:pie_chart/pie_chart.dart'; + +class CustomPieChart extends StatelessWidget { + final Map data; + final List colors; + + const CustomPieChart({ + Key? key, + required this.data, + required this.colors, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return PieChart( + dataMap: data, + animationDuration: const Duration(milliseconds: 800), + chartRadius: MediaQuery.of(context).size.width / 3, + colorList: colors, + initialAngleInDegree: 270, + chartType: ChartType.ring, + ringStrokeWidth: 12, + legendOptions: const LegendOptions( + showLegends: false + ), + chartValuesOptions: const ChartValuesOptions( + showChartValues: false, + ), + ); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index d0842e2..c647bf5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -478,6 +478,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.0" + pie_chart: + dependency: "direct main" + description: + name: pie_chart + sha256: "5dba6d0eb4718e8ed00a9079361cd8947c3f84ac5a5d76f05a27f4ec5e27589e" + url: "https://pub.dev" + source: hosted + version: "5.3.2" plugin_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d18eb72..2445ec4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,6 +73,7 @@ dependencies: sentry_flutter: ^7.9.0 flutter_dotenv: ^5.0.2 flutter_reorderable_list: ^1.3.1 + pie_chart: ^5.3.2 dev_dependencies: flutter_test: From 8c0a5bc98b6c654c6fc5cbdf30ccafa56e9f8c6d Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 21 Oct 2023 21:17:28 +0200 Subject: [PATCH 027/177] Added settings config --- lib/l10n/app_en.arb | 4 ++- lib/l10n/app_es.arb | 4 ++- lib/providers/app_config_provider.dart | 23 +++++++++++++ lib/screens/home/top_items.dart | 6 ++++ .../general_settings/general_settings.dart | 22 +++++++++++++ lib/services/db/database.dart | 33 ++++++++++++++++--- 6 files changed, 86 insertions(+), 6 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index b9c7b1e..451e4e1 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -665,5 +665,7 @@ "discardChangesDescription": "Are you sure you want to discard the changes?", "others": "Others", "showChart": "Show chart", - "hideChart": "Hide 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." } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index fe13bcd..4276b7b 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -665,5 +665,7 @@ "discardChangesDescription": "¿Estás seguro de que deseas descartar los cambios realizados?", "others": "Otros", "showChart": "Mostrar gráfico", - "hideChart": "Ocultar 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." } \ No newline at end of file diff --git a/lib/providers/app_config_provider.dart b/lib/providers/app_config_provider.dart index 3522edc..16e855f 100644 --- a/lib/providers/app_config_provider.dart +++ b/lib/providers/app_config_provider.dart @@ -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 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 setDoNotRememberVersion(String value) async { final updated = await updateConfigQuery( @@ -424,6 +446,7 @@ 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.from( diff --git a/lib/screens/home/top_items.dart b/lib/screens/home/top_items.dart index 9cacd68..24dd6db 100644 --- a/lib/screens/home/top_items.dart +++ b/lib/screens/home/top_items.dart @@ -46,6 +46,12 @@ class _TopItemsState extends State { Colors.grey ]; + @override + void initState() { + _showChart = Provider.of(context, listen: false).showTopItemsChart; + super.initState(); + } + @override Widget build(BuildContext context) { final statusProvider = Provider.of(context); diff --git a/lib/screens/settings/general_settings/general_settings.dart b/lib/screens/settings/general_settings/general_settings.dart index c5ba7cb..9ead014 100644 --- a/lib/screens/settings/general_settings/general_settings.dart +++ b/lib/screens/settings/general_settings/general_settings.dart @@ -191,6 +191,28 @@ class _GeneralSettingsState extends State { builder: (context) => const ReorderableTopItemsHome() )), ), + CustomListTile( + icon: Icons.donut_large_rounded, + title: AppLocalizations.of(context)!.showTopItemsChart, + subtitle: AppLocalizations.of(context)!.showTopItemsChartDescription, + trailing: Switch( + value: appConfigProvider.showTopItemsChart, + onChanged: (value) => updateSettings( + newStatus: value, + function: appConfigProvider.setShowTopItemsChart + ), + ), + onTap: () => updateSettings( + newStatus: !appConfigProvider.showTopItemsChart, + function: appConfigProvider.setShowTopItemsChart + ), + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 16, + right: 10 + ) + ), SectionLabel(label: AppLocalizations.of(context)!.logs), CustomListTile( icon: Icons.timer_rounded, diff --git a/lib/services/db/database.dart b/lib/services/db/database.dart index 5fed50f..7d8cf13 100644 --- a/lib/services/db/database.dart +++ b/lib/services/db/database.dart @@ -116,9 +116,20 @@ Future> loadDb(bool acceptsDynamicTheme) async { }); } + Future upgradeDbToV10(Database db) async { + await db.execute("ALTER TABLE appConfig ADD COLUMN showTopItemsChart NUMERIC"); + await db.execute("UPDATE appConfig SET showTopItemsChart = 1"); + + await db.transaction((txn) async{ + await txn.rawQuery( + 'SELECT * FROM appConfig', + ); + }); + } + Database db = await openDatabase( 'adguard_home_manager.db', - version: 9, + version: 10, onCreate: (Database db, int version) async { await db.execute( """ @@ -154,7 +165,8 @@ Future> loadDb(bool acceptsDynamicTheme) async { combinedChart NUMERIC, doNotRememberVersion TEXT, hideServerAddress NUMERIC, - homeTopItemsOrder TEXT + homeTopItemsOrder TEXT, + showTopItemsChart NUMERIC ) """ ); @@ -173,7 +185,8 @@ Future> loadDb(bool acceptsDynamicTheme) async { showIpLogs, combinedChart, hideServerAddress, - homeTopItemsOrder + homeTopItemsOrder, + showTopItemsChart ) VALUES ( 0, @@ -186,7 +199,8 @@ Future> loadDb(bool acceptsDynamicTheme) async { 0, 0, 0, - '$homeTopItemsDefaultOrderString' + '$homeTopItemsDefaultOrderString', + 1 ) """ ); @@ -201,6 +215,7 @@ Future> loadDb(bool acceptsDynamicTheme) async { await upgradeDbToV7(db); await upgradeDbToV8(db); await upgradeDbToV9(db); + await upgradeDbToV10(db); } if (oldVersion == 2) { await upgradeDbToV3(db); @@ -210,6 +225,7 @@ Future> loadDb(bool acceptsDynamicTheme) async { await upgradeDbToV7(db); await upgradeDbToV8(db); await upgradeDbToV9(db); + await upgradeDbToV10(db); } if (oldVersion == 3) { await upgradeDbToV4(db); @@ -218,6 +234,7 @@ Future> loadDb(bool acceptsDynamicTheme) async { await upgradeDbToV7(db); await upgradeDbToV8(db); await upgradeDbToV9(db); + await upgradeDbToV10(db); } if (oldVersion == 4) { await upgradeDbToV5(db); @@ -225,24 +242,32 @@ Future> loadDb(bool acceptsDynamicTheme) async { await upgradeDbToV7(db); await upgradeDbToV8(db); await upgradeDbToV9(db); + await upgradeDbToV10(db); } if (oldVersion == 5) { await upgradeDbToV6(db); await upgradeDbToV7(db); await upgradeDbToV8(db); await upgradeDbToV9(db); + await upgradeDbToV10(db); } if (oldVersion == 6) { await upgradeDbToV7(db); await upgradeDbToV8(db); await upgradeDbToV9(db); + await upgradeDbToV10(db); } if (oldVersion == 7) { await upgradeDbToV8(db); await upgradeDbToV9(db); + await upgradeDbToV10(db); } if (oldVersion == 8) { await upgradeDbToV9(db); + await upgradeDbToV10(db); + } + if (oldVersion == 9) { + await upgradeDbToV10(db); } }, onOpen: (Database db) async { From 14b7232e8aa199dcf0c9c99fd9a1b53c2d174520 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 21 Oct 2023 21:19:40 +0200 Subject: [PATCH 028/177] Updated readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d8e05f3..864b3de 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,8 @@ On [this repository](https://github.com/JuanRodenas/Pihole_list) you can find a - [async](https://pub.dev/packages/async) - [sentry flutter](https://pub.dev/packages/sentry_flutter) - [flutter dotenv](https://pub.dev/packages/flutter_dotenv) +- [flutter reorderable list](https://pub.dev/packages/flutter_reorderable_list) +- [pie chart](https://pub.dev/packages/pie_chart)
From 3e71abc8dc8fca56e2a9577ac031e84e09469b9e Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 23 Oct 2023 10:32:49 +0200 Subject: [PATCH 029/177] Updated turkish translation --- lib/l10n/app_tr.arb | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index b7b3c93..c89a8b6 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -37,7 +37,7 @@ "connectionRemoved": "Bağlantı başarıyla kaldırıldı", "connectionCannotBeRemoved": "Bağlantı kaldırılamaz.", "remove": "Kaldır", - "removeWarning": "Bu AdGuard Home sunucusuyla bağlantıyı kaldırmak istediğinizden emin misiniz?", + "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", @@ -45,7 +45,7 @@ "delete": "Sil", "save": "Kaydet", "serverStatus": "Sunucu durumu", - "connectionNotUpdated": "Bağlantı güncellenmedi", + "connectionNotUpdated": "Bağlantı Güncellenmedi", "ruleFilteringWidget": "Kural filtreleme bileşeni", "safeBrowsingWidget": "Güvenli gezinti bileşeni", "parentalFilteringWidget": "Ebeveyn filtreleme bileşeni", @@ -78,7 +78,7 @@ "helperPath": "Ters proxy kullanıyorsanız", "aboutApp": "Uygulama hakkında", "appVersion": "Uygulama sürümü", - "createdBy": "Tarafından oluşturuldu", + "createdBy": "Yapımcı", "clients": "İstemciler", "allowed": "İzin verildi", "blocked": "Engellendi", @@ -159,7 +159,7 @@ "notSelected": "Seçili değil", "resetFilters": "Filtreleri sıfırla", "noLogsDisplay": "Gösterilecek günlük yok", - "noLogsThatOld": "Seçilen zamanda kaydedilmiş herhangi bir kayıt olmayabilir. Daha yakın bir zamanı seçmeyi deneyin.", + "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", @@ -264,7 +264,7 @@ "loadingServerInfo": "Sunucu bilgisi yükleniyor...", "serverInfoNotLoaded": "Sunucu bilgisi yüklenemedi.", "dnsAddresses": "DNS adresleri", - "seeDnsAddresses": "DNS adreslerine bakın", + "seeDnsAddresses": "DNS adreslerine bak", "dnsPort": "DNS bağlantı noktası", "httpPort": "HTTP bağlantı noktası", "protectionEnabled": "Koruma etkin", @@ -308,8 +308,8 @@ "addImportant": "Ekle $important", "howCreateRules": "Özel kurallar nasıl oluşturulur?", "examples": "Örnekler", - "example1": "example.org ve tüm alt alanlarına erişimi engeller.", - "example2": "example.org ve tüm alt alanlarına erişimi engellemeyi kaldırır.", + "example1": "ornek.org ve tüm alt alanlarına erişimi engeller.", + "example2": "ornek.org ve tüm alt alanları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", @@ -409,7 +409,7 @@ "logsSettings": "Günlük ayarları", "enableLog": "Günlüğü etkinleştir", "clearLogs": "Günlükleri temizle", - "anonymizeClientIp": "İstemci IP'sini anonimleştirin", + "anonymizeClientIp": "İstemci IP'sini anonimleştir", "hours6": "6 saat", "days30": "30 gün", "days90": "90 gün", @@ -435,11 +435,11 @@ "loadBalancing": "Yük dengeleme", "parallelRequests": "Paralel istekler", "fastestIpAddress": "En hızlı IP adresi", - "loadBalancingDescription": "Her seferinde bir üst akış sunucusuna sorgu yap. AdGuard Home, en hızlı sunucunun daha sık kullanılması için ağırlıklı rasgele bir algoritma kullanır.", - "parallelRequestsDescription": "Çözümlemeyi hızlandırmak için tüm üst akış sunucularına aynı anda sorgu yaparak paralel sorguları kullanın.", + "loadBalancingDescription": "Her seferinde bir üst akış 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 akış 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ı, yukarı akışlarda belirttiğiniz DoH/DoT çözümleyicilerinin IP adreslerini çözmek için kullanılır.", + "bootstrapDnsServersInfo": "Önyükleme ​​DNS sunucuları, üst akışlarda 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", @@ -451,7 +451,7 @@ "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 yukarı akış sunucuları) istemcilerin ana bilgisayar adlarını tersine çöz.", "dnsServerSettings": "AdGuard Home DNS sunucusu ayarları", "limitRequestsSecond": "Saniye başına hız limiti", - "valueNotNumber": "Değer bir rakam değildir", + "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üğünde kaydet.", "enableDnssec": "DNSSEC'i Etkinleştir", @@ -555,7 +555,7 @@ "invalidCertificateChain": "Geçersiz sertifika zinciri", "validCertificateChain": "Geçerli sertifika zinciri", "subject": "Konu", - "issuer": "İhraççı", + "issuer": "Veren", "expires": "Süresi dolacak", "validPrivateKey": "Geçerli özel anahtar", "expirationDate": "Son kullanma tarihi", From 2c8b5b9c6d18e81033d21eb87a6ad3bdfea4e408 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 23 Oct 2023 18:08:02 +0200 Subject: [PATCH 030/177] Updated turkish translation --- lib/l10n/app_tr.arb | 99 ++++++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index c89a8b6..08a7842 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -46,10 +46,10 @@ "save": "Kaydet", "serverStatus": "Sunucu durumu", "connectionNotUpdated": "Bağlantı Güncellenmedi", - "ruleFilteringWidget": "Kural filtreleme bileşeni", - "safeBrowsingWidget": "Güvenli gezinti bileşeni", - "parentalFilteringWidget": "Ebeveyn filtreleme bileşeni", - "safeSearchWidget": "Güvenli arama bileşeni", + "ruleFilteringWidget": "Kural filtreleme", + "safeBrowsingWidget": "Güvenli gezinti", + "parentalFilteringWidget": "Ebeveyn filtreleme", + "safeSearchWidget": "Güvenli arama", "ruleFiltering": "Kural filtreleme", "safeBrowsing": "Güvenli gezinti", "parentalFiltering": "Ebeveyn filtreleme", @@ -57,15 +57,15 @@ "serverStatusNotRefreshed": "Sunucu durumu yenilenemedi", "loadingStatus": "Durum yükleniyor...", "errorLoadServerStatus": "Sunucu durumu yüklenemedi", - "topQueriedDomains": "En çok sorgulanan alan adları", + "topQueriedDomains": "En çok sorgulananlar", "viewMore": "Daha fazla göster", "topClients": "Öne çıkan istemciler", - "topBlockedDomains": "En çok engellenen alan adları", + "topBlockedDomains": "En çok engellenenler", "appSettings": "Uygulama ayarları", "theme": "Tema", "light": "Aydınlık", "dark": "Karanlık", - "systemDefined": "Tanımlanmış sistem", + "systemDefined": "Sistem temasına uy", "close": "Kapat", "connectedTo": "Bağlandı:", "selectedServer": "Seçili sunucu:", @@ -102,7 +102,7 @@ "copyLogsClipboard": "Günlükleri panoya kopyala", "logsCopiedClipboard": "Günlükler panoya kopyalandı", "advancedSettings": "Gelişmiş ayarlar", - "dontCheckCertificate": "SSL sertifikasını kontrol etme", + "dontCheckCertificate": "SSL sertifikası kontrol edilmesin", "dontCheckCertificateDescription": "Sunucunun SSL sertifikası doğrulamasını geçersiz kılar", "advancedSetupDescription": "Gelişmiş seçenekler", "settingsUpdatedSuccessfully": "Ayarlar başarıyla güncellendi.", @@ -143,7 +143,7 @@ "responseCode": "Yanıt kodu", "client": "İstemci", "deviceIp": "IP adresi", - "deviceName": "Ad", + "deviceName": "İstemci adı", "logDetails": "Günlük detayları", "blockingRule": "Engelleme kuralı", "blockDomain": "Alan adını engelle", @@ -170,9 +170,9 @@ "search": "Ara", "dnsQueries": "DNS sorguları", "average": "Ortalama", - "blockedFilters": "Filtreler tarafından engellenen", - "malwarePhisingBlocked": "Engellenen zararlı yazılım/oltalama ", - "blockedAdultWebsites": "Engellenen yetişkin siteleri", + "blockedFilters": "Engellenen (Alan adları)", + "malwarePhisingBlocked": "Engellenen (Zararlı içerik)", + "blockedAdultWebsites": "Engellenen (Yetişkin içerik)", "generalSettings": "Genel ayarlar", "generalSettingsDescription": "Çeşitli farklı ayarlar", "hideZeroValues": "Sıfır değerlerini gizle", @@ -190,9 +190,9 @@ "noIdentifiers": "Tanımlayıcı eklenmedi", "useGlobalSettings": "Küresel ayarları kullan", "enableFiltering": "Filtrelemeyi etkinleştir", - "enableSafeBrowsing": "Güvenli gezintiyi etkinleştirin", - "enableParentalControl": "Ebeveyn kontrolünü etkinleştirinl", - "enableSafeSearch": "Güvenli aramayı etkinleştirin", + "enableSafeBrowsing": "Güvenli gezintiyi etkinleştir", + "enableParentalControl": "Ebeveyn kontrolünü etkinleştir", + "enableSafeSearch": "Güvenli aramayı etkinleştir", "blockedServices": "Engellenen hizmetler", "selectBlockedServices": "Engellenen hizmetleri seç", "noBlockedServicesSelected": "Engellenen hizmetler seçilmedi", @@ -214,8 +214,8 @@ "whitelists": "Beyaz listeler", "blacklists": "Kara listeler", "rules": "Kurallar", - "customRules": "Özel kural", - "enabledRules": "Etkin kural", + "customRules": "Özel kurallar", + "enabledRules": "Etkin kurallar", "enabled": "Etkin", "disabled": "Devre dışı", "rule": "Kural", @@ -233,12 +233,12 @@ "noWhiteLists": "Beyaz listeler yok", "addWhitelist": "Beyaz liste ekle", "addBlacklist": "Kara liste ekle", - "urlNotValid": "URL geçerli değil", - "urlAbsolutePath": "URL veya kesin dosya yolu", + "urlNotValid": "URL Adresi geçerli değil", + "urlAbsolutePath": "URL adresi veya kesin dosya yolu", "addingList": "Liste ekleniyor...", "listAdded": "Liste başarıyla eklendi. Eklenen öğeler:", "listAlreadyAdded": "Liste zaten eklenmiş", - "listUrlInvalid": "Liste URL'si geçersiz", + "listUrlInvalid": "Liste URL adresi geçersiz", "listNotAdded": "Liste eklenemedi", "listDetails": "Liste detayları", "listType": "Liste türü", @@ -284,7 +284,7 @@ "noAllowedClients": "İzin verilen istemci yok", "allowedClientsDescription": "Eğer bu liste girişlere sahipse, AdGuard Home sadece bu istemcilerden gelen istekleri kabul edecek.", "blockedClientsDescription": "Eğer bu liste girişlere sahipse, AdGuard Home bu istemcilerden gelen istekleri reddedecektir. Bu alan, İzin Verilen İstemciler bölümünde girişler varsa dikkate alınmaz.", - "disallowedDomainsDescription": "AdGuard Home, bu alanlara uyan DNS sorgularını reddeder ve bu sorgular sorgu günlüğünde bile görünmez.", + "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", @@ -305,11 +305,11 @@ "block": "Engelle", "unblock": "Engeli kaldır", "custom": "Özel", - "addImportant": "Ekle $important", + "addImportant": "Ekle ($important)", "howCreateRules": "Özel kurallar nasıl oluşturulur?", "examples": "Örnekler", - "example1": "ornek.org ve tüm alt alanlarına erişimi engeller.", - "example2": "ornek.org ve tüm alt alanlarına erişimi engellemeyi kaldırır.", + "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", @@ -336,7 +336,7 @@ "updating": "Değerler güncelleniyor...", "blockedServicesUpdated": "Engellenen hizmetler başarıyla güncellendi", "blockedServicesNotUpdated": "Engellenen hizmetler güncellenemedi", - "insertDomain": "Durumunu kontrol etmek için bir alan adı ekleyin.", + "insertDomain": "Durumu kontrol etmek için bir alan adı ekleyin.", "dhcpSettings": "DHCP ayarları", "dhcpSettingsDescription": "DHCP sunucusunu yapılandır", "dhcpSettingsNotLoaded": "DHCP ayarları yüklenemedi", @@ -422,7 +422,7 @@ "deletingLogs": "Günlükler temizleniyor...", "logsCleared": "Günlükler başarıyla temizlendi", "logsNotCleared": "Günlükler temizlenemedi", - "runningHomeAssistant": "Ev Asistanı üzerinde çalışıyor", + "runningHomeAssistant": "Ev Asistanı üzerinde çalıştır", "serverError": "Sunucu hatası", "noItems": "Burada gösterilecek öğe yok", "dnsSettings": "DNS ayarları", @@ -445,19 +445,19 @@ "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 kullanma", + "usePrivateReverseDnsResolvers": "Özel ters DNS çözümleyicilerini kullan", "usePrivateReverseDnsResolversDescription": "Bu üst akış 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ştirin", + "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 yukarı akış sunucuları) istemcilerin ana bilgisayar adlarını tersine çöz.", "dnsServerSettings": "AdGuard Home DNS sunucusu ayarları", "limitRequestsSecond": "Saniye başına hız limiti", "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üğünde kaydet.", + "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ında DNSSEC bayrağını ayarla ve sonucu kontrol et (DNSSEC etkinleştirilmiş bir çözümleyici gereklidir).", + "enableDnssecDescription": "Giden DNS sorguları için DNSSEC özelliğini etkinleştir ve sonucu kontrol et (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ı devre dışı bırakın (AAAA yazın).", + "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.", @@ -471,7 +471,7 @@ "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 kullanım süresi değerini uzatın (saniye olarak).", + "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", @@ -504,18 +504,18 @@ "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 cevap verecektir.", "serverConfiguration": "Sunucu yapılandırması", "domainName": "Alan adı", - "domainNameDescription": "Eğer ayarlanırsa, AdGuard Home İstemci 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.", + "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ı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": "Bir sertifika dosyası yolu ayarlayın", + "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ı yolu ayarla", "pasteCertificateContent": "Sertifika içeriğini yapıştır", "certificatePath": "Sertifika dosya yolu", "certificateContent": "Sertifika içeriği", - "privateKey": "Private key", + "privateKey": "Özel anahtar", "privateKeyFile": "Özel bir anahtar dosyası belirle", "pastePrivateKey": "Özel anahtar içeriğini yapıştır", "usePreviousKey": "Önceden kaydedilmiş anahtarı kullan", @@ -550,7 +550,7 @@ "pink": "Pembe", "deepOrange": "Koyu turuncu", "indigo": "Çivit mavisi", - "useThemeColorStatus": "Durum için tema rengini kullanın", + "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", @@ -602,9 +602,9 @@ "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} dakikalar", + "minutes": "{time} dakika", "hour": "{time} saat", - "hours": "{time} saatler", + "hours": "{time} saat", "remainingTime": "Kalan süre", "safeSearchSettings": "Güvenli arama ayarları", "loadingSafeSearchSettings": "Güvenli arama ayarları yükleniyor...", @@ -618,14 +618,14 @@ "copiedClipboard": "Panoya kopyalandı", "seeDetails": "Detayları gör", "listNotAvailable": "Liste mevcut değil", - "copyListUrl": "Liste URL'sini kopyala", - "listUrlCopied": "Panoya kopyalanan URL'yi listeleyin", + "copyListUrl": "Liste URL adresini kopyala", + "listUrlCopied": "Panoya kopyalanan URL 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": "IP günlüklerde", + "ipLogs": "Günlüklerdeki IP", "ipLogsDescription": "Günlüklerde istemci adı yerine her zaman IP adresini göster", "application": "Uygulama", "combinedChart": "Birleştirilmiş grafik", @@ -653,14 +653,19 @@ "december": "Aralık", "malwarePhising": "Zararlı yazılım/oltalama", "queries": "Sorgular", - "adultSites": "Yetişkin siteleri", + "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": "En iyi ürün siparişi", - "topItemsOrderDescription": "Ana ekran üst öğe listelerini sırala", + "topItemsOrder": "Öne çıkan öğe 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 edin", - "discardChangesDescription": "Değişiklikleri iptal etmek istediğinizden emin misiniz?" + "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." } \ No newline at end of file From 5a13d5259805cefe6ba2db144d1da4d553c1c1f7 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 23 Oct 2023 18:12:21 +0200 Subject: [PATCH 031/177] Small fix --- lib/screens/clients/client/settings_tile.dart | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/screens/clients/client/settings_tile.dart b/lib/screens/clients/client/settings_tile.dart index 81f0c5c..c2d6ede 100644 --- a/lib/screens/clients/client/settings_tile.dart +++ b/lib/screens/clients/client/settings_tile.dart @@ -30,11 +30,14 @@ class SettingsTile extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - label, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface, + Expanded( + child: Text( + label, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface, + ), ), ), useGlobalSettingsFiltering == false From 639f583046e3edf4028cc4d73dc6e0b7daf130e1 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Tue, 24 Oct 2023 20:59:54 +0200 Subject: [PATCH 032/177] Desktop UI improvements --- lib/screens/home/home.dart | 59 --------------------------------- lib/screens/home/top_items.dart | 49 +++++++++++++++------------ 2 files changed, 27 insertions(+), 81 deletions(-) diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index 59fb0bf..fcac0cc 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -176,65 +176,6 @@ class _HomeState extends State { ), TopItemsLists(order: appConfigProvider.homeTopItemsOrder), - - // 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(), - // ), - // ], - // ) ]; } diff --git a/lib/screens/home/top_items.dart b/lib/screens/home/top_items.dart index 24dd6db..06ef00e 100644 --- a/lib/screens/home/top_items.dart +++ b/lib/screens/home/top_items.dart @@ -157,41 +157,46 @@ class _TopItemsState extends State { const SizedBox(height: 24), if (widget.data.isEmpty) noItems, - if (widget.data.isNotEmpty && width > 700) SizedBox( - height: 240, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - flex: 1, + if (widget.data.isNotEmpty && width > 700) Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + flex: 1, + child: ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 250 + ), child: Padding( padding: const EdgeInsets.all(16), child: chart, - ) - ), - Expanded( - flex: 2, - child: Column( - children: [ - ...itemsList, - OthersRowItem(items: widget.data) - ] ), ) - ], - ), + ), + Expanded( + flex: 2, + child: Column( + children: [ + ...itemsList, + OthersRowItem(items: widget.data) + ] + ), + ) + ], ), if (widget.data.isNotEmpty && width <= 700) ...[ if (_showChart) ...[ - chart, + SizedBox( + height: 300, + child: chart + ), const SizedBox(height: 16), ], ...itemsList, - if (_showChart) OthersRowItem(items: widget.data) + if (_showChart) OthersRowItem(items: widget.data), + const SizedBox(height: 16), ], - if (widget.data.length > 5) ...[ - const SizedBox(height: 20), + if (widget.data.length > 5) ...[ Padding( padding: const EdgeInsets.only(right: 20), child: Row( From 6f3ba647f42b2d8782c38beb9a01cd7e10e01472 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Tue, 24 Oct 2023 21:19:03 +0200 Subject: [PATCH 033/177] Added scrollbar desktop management modal --- lib/screens/home/management_modal.dart | 88 +++++++++++++++----------- 1 file changed, 51 insertions(+), 37 deletions(-) diff --git a/lib/screens/home/management_modal.dart b/lib/screens/home/management_modal.dart index d23ddd4..9b94b58 100644 --- a/lib/screens/home/management_modal.dart +++ b/lib/screens/home/management_modal.dart @@ -31,6 +31,8 @@ class _ManagementModalState extends State with SingleTickerProv late Animation animation; final ExpandableController expandableController = ExpandableController(); + final _chipsScrollController = ScrollController(); + @override void initState() { expandableController.addListener(() async { @@ -155,46 +157,58 @@ class _ManagementModalState extends State with SingleTickerProv Widget bottomRow() { return Container( - height: 40, + height: Platform.isMacOS || Platform.isLinux || Platform.isWindows ? 50 : 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, + 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 ), - 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, + 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 + ? () => 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, + ), + ], ), - 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, - ), - ], + ), ), ); } From 229d329936e6861fa66ea890dc4b766db3b97bd5 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Tue, 24 Oct 2023 21:28:57 +0200 Subject: [PATCH 034/177] Fixed settings sliver list --- lib/screens/settings/settings.dart | 311 +++++++++++++++-------------- 1 file changed, 162 insertions(+), 149 deletions(-) diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index 709a245..2f9a24d 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -129,158 +129,171 @@ class SettingsWidget extends StatelessWidget { ) ) ], - body: ListView( - children: [ - if ( - serversProvider.selectedServer != null && - statusProvider.serverStatus != null && - serversProvider.apiClient != null - ) ...[ - SectionLabel(label: AppLocalizations.of(context)!.serverSettings), - if (serverVersionIsAhead( - currentVersion: statusProvider.serverStatus!.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == true) settingsTile( - icon: Icons.search_rounded, - title: AppLocalizations.of(context)!.safeSearch, - subtitle: AppLocalizations.of(context)!.safeSearchSettings, - thisItem: 0, - screenToNavigate: const SafeSearchSettingsScreen(), - ), - settingsTile( - icon: Icons.lock_rounded, - title: AppLocalizations.of(context)!.accessSettings, - subtitle: AppLocalizations.of(context)!.accessSettingsDescription, - thisItem: 1, - screenToNavigate: const AccessSettings(), - ), - settingsTile( - icon: Icons.install_desktop_rounded, - title: AppLocalizations.of(context)!.dhcpSettings, - subtitle: AppLocalizations.of(context)!.dhcpSettingsDescription, - thisItem: 2, - screenToNavigate: const DhcpScreen(), - ), - settingsTile( - icon: Icons.dns_rounded, - title: AppLocalizations.of(context)!.dnsSettings, - subtitle: AppLocalizations.of(context)!.dnsSettingsDescription, - thisItem: 3, - screenToNavigate: const DnsSettings(), - ), - settingsTile( - icon: Icons.security_rounded, - title: AppLocalizations.of(context)!.encryptionSettings, - subtitle: AppLocalizations.of(context)!.encryptionSettingsDescription, - thisItem: 4, - screenToNavigate: const EncryptionSettings(), - ), - settingsTile( - icon: Icons.route_rounded, - title: AppLocalizations.of(context)!.dnsRewrites, - subtitle: AppLocalizations.of(context)!.dnsRewritesDescription, - thisItem: 5, - screenToNavigate: const DnsRewritesScreen(), - ), - if (serversProvider.updateAvailable.data != null) settingsTile( - icon: Icons.system_update_rounded, - title: AppLocalizations.of(context)!.updates, - subtitle: AppLocalizations.of(context)!.updatesDescription, - trailing: serversProvider.updateAvailable.data != null && - serversProvider.updateAvailable.data!.canAutoupdate == true - ? Container( - width: 10, - height: 10, - margin: const EdgeInsets.only(right: 12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.red - ), - ) - : null, - thisItem: 6, - screenToNavigate: const UpdateScreen(), - ), - settingsTile( - icon: Icons.info_rounded, - title: AppLocalizations.of(context)!.serverInformation, - subtitle: AppLocalizations.of(context)!.serverInformationDescription, - thisItem: 7, - screenToNavigate: const ServerInformation(), - ), - ], - SectionLabel(label: AppLocalizations.of(context)!.appSettings), - settingsTile( - icon: Icons.palette_rounded, - title: AppLocalizations.of(context)!.customization, - subtitle: AppLocalizations.of(context)!.customizationDescription, - thisItem: 8, - screenToNavigate: const Customization(), - ), - settingsTile( - icon: Icons.storage_rounded, - title: AppLocalizations.of(context)!.servers, - subtitle: serversProvider.selectedServer != null - ? statusProvider.serverStatus != null - ? "${AppLocalizations.of(context)!.connectedTo} ${serversProvider.selectedServer!.name}" - : "${AppLocalizations.of(context)!.selectedServer} ${serversProvider.selectedServer!.name}" - : AppLocalizations.of(context)!.noServerSelected, - thisItem: 9, - screenToNavigate: const Servers(), - ), - settingsTile( - icon: Icons.settings, - title: AppLocalizations.of(context)!.generalSettings, - subtitle: AppLocalizations.of(context)!.generalSettingsDescription, - thisItem: 10, - screenToNavigate: const GeneralSettings(), - ), - settingsTile( - icon: Icons.build_outlined, - title: AppLocalizations.of(context)!.advancedSettings, - subtitle: AppLocalizations.of(context)!.advancedSetupDescription, - thisItem: 11, - screenToNavigate: const AdvancedSettings(), - ), - SectionLabel(label: AppLocalizations.of(context)!.aboutApp), - CustomListTile( - title: AppLocalizations.of(context)!.appVersion, - subtitle: appConfigProvider.getAppInfo!.version, - ), - CustomListTile( - title: AppLocalizations.of(context)!.createdBy, - subtitle: Strings.createdBy, - ), - Padding( - padding: const EdgeInsets.all(15), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - if (Platform.isAndroid) IconButton( - onPressed: () => openUrl(Urls.playStore), - icon: SvgPicture.asset( - 'assets/resources/google-play.svg', - color: Theme.of(context).colorScheme.onSurfaceVariant, - width: 30, - height: 30, + body: SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) => CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + SliverList.list( + children: [ + if ( + serversProvider.selectedServer != null && + statusProvider.serverStatus != null && + serversProvider.apiClient != null + ) ...[ + SectionLabel(label: AppLocalizations.of(context)!.serverSettings), + if (serverVersionIsAhead( + currentVersion: statusProvider.serverStatus!.serverVersion, + referenceVersion: 'v0.107.28', + referenceVersionBeta: 'v0.108.0-b.33' + ) == true) settingsTile( + icon: Icons.search_rounded, + title: AppLocalizations.of(context)!.safeSearch, + subtitle: AppLocalizations.of(context)!.safeSearchSettings, + thisItem: 0, + screenToNavigate: const SafeSearchSettingsScreen(), + ), + settingsTile( + icon: Icons.lock_rounded, + title: AppLocalizations.of(context)!.accessSettings, + subtitle: AppLocalizations.of(context)!.accessSettingsDescription, + thisItem: 1, + screenToNavigate: const AccessSettings(), + ), + settingsTile( + icon: Icons.install_desktop_rounded, + title: AppLocalizations.of(context)!.dhcpSettings, + subtitle: AppLocalizations.of(context)!.dhcpSettingsDescription, + thisItem: 2, + screenToNavigate: const DhcpScreen(), + ), + settingsTile( + icon: Icons.dns_rounded, + title: AppLocalizations.of(context)!.dnsSettings, + subtitle: AppLocalizations.of(context)!.dnsSettingsDescription, + thisItem: 3, + screenToNavigate: const DnsSettings(), + ), + settingsTile( + icon: Icons.security_rounded, + title: AppLocalizations.of(context)!.encryptionSettings, + subtitle: AppLocalizations.of(context)!.encryptionSettingsDescription, + thisItem: 4, + screenToNavigate: const EncryptionSettings(), + ), + settingsTile( + icon: Icons.route_rounded, + title: AppLocalizations.of(context)!.dnsRewrites, + subtitle: AppLocalizations.of(context)!.dnsRewritesDescription, + thisItem: 5, + screenToNavigate: const DnsRewritesScreen(), + ), + if (serversProvider.updateAvailable.data != null) settingsTile( + icon: Icons.system_update_rounded, + title: AppLocalizations.of(context)!.updates, + subtitle: AppLocalizations.of(context)!.updatesDescription, + trailing: serversProvider.updateAvailable.data != null && + serversProvider.updateAvailable.data!.canAutoupdate == true + ? Container( + width: 10, + height: 10, + margin: const EdgeInsets.only(right: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.red + ), + ) + : null, + thisItem: 6, + screenToNavigate: const UpdateScreen(), + ), + settingsTile( + icon: Icons.info_rounded, + title: AppLocalizations.of(context)!.serverInformation, + subtitle: AppLocalizations.of(context)!.serverInformationDescription, + thisItem: 7, + screenToNavigate: const ServerInformation(), + ), + ], + SectionLabel(label: AppLocalizations.of(context)!.appSettings), + settingsTile( + icon: Icons.palette_rounded, + title: AppLocalizations.of(context)!.customization, + subtitle: AppLocalizations.of(context)!.customizationDescription, + thisItem: 8, + screenToNavigate: const Customization(), ), - tooltip: AppLocalizations.of(context)!.visitGooglePlay, - ), - IconButton( - onPressed: () => openUrl(Urls.gitHub), - icon: SvgPicture.asset( - 'assets/resources/github.svg', - color: Theme.of(context).colorScheme.onSurfaceVariant, - width: 30, - height: 30, + settingsTile( + icon: Icons.storage_rounded, + title: AppLocalizations.of(context)!.servers, + subtitle: serversProvider.selectedServer != null + ? statusProvider.serverStatus != null + ? "${AppLocalizations.of(context)!.connectedTo} ${serversProvider.selectedServer!.name}" + : "${AppLocalizations.of(context)!.selectedServer} ${serversProvider.selectedServer!.name}" + : AppLocalizations.of(context)!.noServerSelected, + thisItem: 9, + screenToNavigate: const Servers(), ), - tooltip: AppLocalizations.of(context)!.gitHub, - ), - ], - ), + settingsTile( + icon: Icons.settings, + title: AppLocalizations.of(context)!.generalSettings, + subtitle: AppLocalizations.of(context)!.generalSettingsDescription, + thisItem: 10, + screenToNavigate: const GeneralSettings(), + ), + settingsTile( + icon: Icons.build_outlined, + title: AppLocalizations.of(context)!.advancedSettings, + subtitle: AppLocalizations.of(context)!.advancedSetupDescription, + thisItem: 11, + screenToNavigate: const AdvancedSettings(), + ), + SectionLabel(label: AppLocalizations.of(context)!.aboutApp), + CustomListTile( + title: AppLocalizations.of(context)!.appVersion, + subtitle: appConfigProvider.getAppInfo!.version, + ), + CustomListTile( + title: AppLocalizations.of(context)!.createdBy, + subtitle: Strings.createdBy, + ), + Padding( + padding: const EdgeInsets.all(15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (Platform.isAndroid) IconButton( + onPressed: () => openUrl(Urls.playStore), + icon: SvgPicture.asset( + 'assets/resources/google-play.svg', + color: Theme.of(context).colorScheme.onSurfaceVariant, + width: 30, + height: 30, + ), + tooltip: AppLocalizations.of(context)!.visitGooglePlay, + ), + IconButton( + onPressed: () => openUrl(Urls.gitHub), + icon: SvgPicture.asset( + 'assets/resources/github.svg', + color: Theme.of(context).colorScheme.onSurfaceVariant, + width: 30, + height: 30, + ), + tooltip: AppLocalizations.of(context)!.gitHub, + ), + ], + ), + ) + ], + ) + ], ) - ], + ), ), ) ); From 211a0b02980823f030c26d3918200c1fcae178fa Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Tue, 24 Oct 2023 21:43:24 +0200 Subject: [PATCH 035/177] Fixed typo --- lib/l10n/app_en.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 451e4e1..ee0533d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -59,7 +59,7 @@ "errorLoadServerStatus": "Server status could not be loaded", "topQueriedDomains": "Queried domains", "viewMore": "View more", - "topClients": "Cients", + "topClients": "Clients", "topBlockedDomains": "Blocked domains", "appSettings": "App settings", "theme": "Theme", From 80b3c004d62199f8c9825f4735bc249105cc4b1b Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 27 Oct 2023 14:39:07 +0200 Subject: [PATCH 036/177] Fixed GitHub update notifier --- lib/constants/urls.dart | 1 + lib/functions/check_app_updates.dart | 24 +++++---- lib/functions/compare_versions.dart | 12 ++--- lib/services/http_requests.dart | 73 +++++++++++++++++++++++++++- 4 files changed, 92 insertions(+), 18 deletions(-) diff --git a/lib/constants/urls.dart b/lib/constants/urls.dart index 440c8d7..b7eb069 100644 --- a/lib/constants/urls.dart +++ b/lib/constants/urls.dart @@ -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"; diff --git a/lib/functions/check_app_updates.dart b/lib/functions/check_app_updates.dart index eb44177..306b2ea 100644 --- a/lib/functions/check_app_updates.dart +++ b/lib/functions/check_app_updates.dart @@ -12,21 +12,27 @@ Future checkAppUpdates({ required Source installationSource, required bool isBeta }) async { - final result = await checkAppUpdatesGitHub(); + var result = isBeta + ? await getReleasesGitHub() + : await getLatestReleaseGitHub(); if (result['result'] == 'success') { + late GitHubRelease gitHubRelease; + if (isBeta) { + gitHubRelease = (result['body'] as List).firstWhere((r) => r.prerelease == true); + } + else { + gitHubRelease = result['body'] as GitHubRelease; + } + final update = gitHubUpdateExists( currentBuildNumber: currentBuildNumber, - gitHubReleases: result['body'], + gitHubRelease: gitHubRelease, isBeta: isBeta ); 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 checkAppUpdates({ installationSource == Source.IS_INSTALLED_FROM_PLAY_PACKAGE_INSTALLER || installationSource == Source.UNKNOWN ) { - return release; + return gitHubRelease; } else { return null; @@ -44,7 +50,7 @@ Future checkAppUpdates({ return null; } else { - return release; + return gitHubRelease; } } else { diff --git a/lib/functions/compare_versions.dart b/lib/functions/compare_versions.dart index 33ae8fe..b2f37e1 100644 --- a/lib/functions/compare_versions.dart +++ b/lib/functions/compare_versions.dart @@ -151,15 +151,11 @@ bool serverVersionIsAhead({ bool gitHubUpdateExists({ required String currentBuildNumber, - required List 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; } } \ No newline at end of file diff --git a/lib/services/http_requests.dart b/lib/services/http_requests.dart index e1f4552..7fe18e0 100644 --- a/lib/services/http_requests.dart +++ b/lib/services/http_requests.dart @@ -2322,7 +2322,7 @@ class ApiClient { } } -Future checkAppUpdatesGitHub() async { +Future getReleasesGitHub() async { try { HttpClient httpClient = HttpClient(); HttpClientRequest request = await httpClient.getUrl(Uri.parse(Urls.getReleasesGitHub)); @@ -2391,4 +2391,75 @@ Future checkAppUpdatesGitHub() async { ) }; } +} + +Future getLatestReleaseGitHub() async { + try { + HttpClient httpClient = HttpClient(); + HttpClientRequest request = await httpClient.getUrl(Uri.parse(Urls.getLatestReleaseGitHub)); + HttpClientResponse response = await request.close(); + String reply = await response.transform(utf8.decoder).join(); + httpClient.close(); + if (response.statusCode == 200) { + return { + 'result': 'success', + 'hasResponse': true, + 'error': false, + 'statusCode': response.statusCode, + 'body': GitHubRelease.fromJson(jsonDecode(reply)) + }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'update_encryption_settings', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: response.statusCode.toString(), + resBody: reply, + ) + }; + } + } on SocketException { + return { + 'result': 'no_connection', + 'message': 'SocketException', + 'log': AppLog( + type: 'check_latest_release_github', + dateTime: DateTime.now(), + message: 'SocketException' + ) + }; + } on TimeoutException { + return { + 'result': 'no_connection', + 'message': 'TimeoutException', + 'log': AppLog( + type: 'check_latest_release_github', + dateTime: DateTime.now(), + message: 'TimeoutException' + ) + }; + } on HandshakeException { + return { + 'result': 'ssl_error', + 'message': 'HandshakeException', + 'log': AppLog( + type: 'check_latest_release_github', + dateTime: DateTime.now(), + message: 'HandshakeException' + ) + }; + } catch (e) { + return { + 'result': 'error', + 'message': e.toString(), + 'log': AppLog( + type: 'check_latest_release_github', + dateTime: DateTime.now(), + message: e.toString() + ) + }; + } } \ No newline at end of file From 2ad739ed4fe38103b9eec0ec28630f0e2f27d7b3 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 27 Oct 2023 14:39:53 +0200 Subject: [PATCH 037/177] Updated turkish translation --- lib/l10n/app_tr.arb | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index 08a7842..ef6edd7 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -139,7 +139,7 @@ "clas": "Sınıf", "response": "Yanıt", "dnsServer": "DNS sunucusu", - "elapsedTime": "Zaman aşımı", + "elapsedTime": "İşlem süresi", "responseCode": "Yanıt kodu", "client": "İstemci", "deviceIp": "IP adresi", @@ -171,7 +171,7 @@ "dnsQueries": "DNS sorguları", "average": "Ortalama", "blockedFilters": "Engellenen (Alan adları)", - "malwarePhisingBlocked": "Engellenen (Zararlı içerik)", + "malwarePhishingBlocked": "Engellenen (Zararlı içerik)", "blockedAdultWebsites": "Engellenen (Yetişkin içerik)", "generalSettings": "Genel ayarlar", "generalSettingsDescription": "Çeşitli farklı ayarlar", @@ -267,9 +267,9 @@ "seeDnsAddresses": "DNS adreslerine bak", "dnsPort": "DNS bağlantı noktası", "httpPort": "HTTP bağlantı noktası", - "protectionEnabled": "Koruma etkin", - "dhcpAvailable": "DHCP mevcut", - "serverRunning": "Sunucu çalışıyor", + "protectionEnabled": "Koruma etkin mi?", + "dhcpAvailable": "DHCP mevcut mu?", + "serverRunning": "Sunucu çalışıyor mu?", "serverVersion": "Sunucu sürümü", "serverLanguage": "Sunucu dili", "yes": "Evet", @@ -281,9 +281,9 @@ "accessSettingsDescription": "Sunucu için erişim kurallarını yapılandır", "loadingClients": "İstemciler yükleniyor...", "clientsNotLoaded": "İstemciler yüklenemedi.", - "noAllowedClients": "İzin verilen istemci yok", - "allowedClientsDescription": "Eğer bu liste girişlere sahipse, AdGuard Home sadece bu istemcilerden gelen istekleri kabul edecek.", - "blockedClientsDescription": "Eğer bu liste girişlere sahipse, AdGuard Home bu istemcilerden gelen istekleri reddedecektir. Bu alan, İzin Verilen İstemciler bölümünde girişler varsa dikkate alınmaz.", + "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 girdiler içeriyorsa, AdGuard Home bu istemcilerden gelen talepleri reddedecektir. Bu alan, İ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ı", @@ -396,7 +396,7 @@ "dnsRewritesDescription": "Özel DNS kurallarını yapılandır", "loadingRewriteRules": "Yeniden yazma kuralları yükleniyor...", "rewriteRulesNotLoaded": "DNS yeniden yazma kuralları yüklenemedi.", - "noRewriteRules": "DNS yeniden yazma kuralları yok", + "noRewriteRules": "DNS yeniden yazım kuralları yok", "answer": "Cevap", "deleteDnsRewrite": "DNS yeniden yazmayı sil", "deleteDnsRewriteMessage": "Bu DNS yeniden yazmasını silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", @@ -511,15 +511,15 @@ "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ı yolu ayarla", + "certificateFilePath": "Sertifika dosyası yolunu ayarla", "pasteCertificateContent": "Sertifika içeriğini yapıştır", "certificatePath": "Sertifika dosya yolu", "certificateContent": "Sertifika içeriği", "privateKey": "Özel anahtar", - "privateKeyFile": "Özel bir anahtar dosyası belirle", + "privateKeyFile": "Özel anahtar dosyası belirle", "pastePrivateKey": "Özel anahtar içeriğini yapıştır", "usePreviousKey": "Önceden kaydedilmiş anahtarı kullan", - "privateKeyPath": "Özel anahtar yolu", + "privateKeyPath": "Özel anahtar dosya yolu", "invalidCertificate": "Geçersiz sertifika", "invalidPrivateKey": "Geçersiz özel anahtar", "validatingData": "Veri doğrulama", @@ -561,7 +561,7 @@ "expirationDate": "Son kullanma tarihi", "keysNotMatch": "Geçersiz bir sertifika veya anahtar: tls: özel anahtar genel anahtarla eşleşmiyor.", "timeLogs": "Günlüklerdeki zaman", - "timeLogsDescription": "Günlükler listesinde işlem süresini göster", + "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", @@ -651,14 +651,14 @@ "october": "Ekim", "november": "Kasım", "december": "Aralık", - "malwarePhising": "Zararlı yazılım/oltalama", + "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 öğe sıralaması", + "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", @@ -668,4 +668,4 @@ "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." -} \ No newline at end of file +} From 621171c5b1940bc8e05b58370d59799f078eb1c9 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 28 Oct 2023 22:38:49 +0200 Subject: [PATCH 038/177] Implemented go router --- lib/base.dart | 2 +- lib/config/app_screens.dart | 7 - lib/config/sizes.dart | 1 + lib/constants/routes_names.dart | 19 ++ lib/functions/desktop_mode.dart | 5 + lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/main.dart | 7 +- lib/models/app_screen.dart | 2 - lib/routes/router.dart | 20 ++ lib/routes/router_globals.dart | 10 + lib/routes/routes.dart | 113 ++++++++ lib/screens/clients/added_list.dart | 3 - .../clients/client/client_placeholder.dart | 12 + .../clients/client/logs_list_client.dart | 44 ++- lib/screens/clients/clients.dart | 263 ++++++++---------- lib/screens/clients/clients_desktop_view.dart | 19 +- lib/screens/clients/clients_list.dart | 3 - lib/screens/home/appbar.dart | 4 + lib/widgets/bottom_nav_bar.dart | 30 +- lib/widgets/layout.dart | 250 +++++++++++++++++ pubspec.lock | 24 ++ pubspec.yaml | 2 + 23 files changed, 614 insertions(+), 234 deletions(-) create mode 100644 lib/config/sizes.dart create mode 100644 lib/constants/routes_names.dart create mode 100644 lib/functions/desktop_mode.dart create mode 100644 lib/routes/router.dart create mode 100644 lib/routes/router_globals.dart create mode 100644 lib/routes/routes.dart create mode 100644 lib/screens/clients/client/client_placeholder.dart create mode 100644 lib/widgets/layout.dart diff --git a/lib/base.dart b/lib/base.dart index b2b593b..d57dd72 100644 --- a/lib/base.dart +++ b/lib/base.dart @@ -101,7 +101,7 @@ class _BaseState extends State with WidgetsBindingObserver { child: child, ) ), - child: screens[appConfigProvider.selectedScreen].body, + child: SizedBox() ), ), ], diff --git a/lib/config/app_screens.dart b/lib/config/app_screens.dart index 1835dc4..91d33f0 100644 --- a/lib/config/app_screens.dart +++ b/lib/config/app_screens.dart @@ -13,12 +13,10 @@ List screensSelectServer = [ const AppScreen( name: "connect", icon: Icons.link_rounded, - body: Connect(), ), const AppScreen( name: "settings", icon: Icons.settings_rounded, - body: Settings() ) ]; @@ -26,26 +24,21 @@ List screensServerConnected = [ const AppScreen( name: "home", icon: Icons.home_rounded, - body: Home(), ), const AppScreen( name: "clients", icon: Icons.devices, - body: Clients() ), const AppScreen( name: "logs", icon: Icons.list_alt_rounded, - body: Logs(), ), const AppScreen( name: "filters", icon: Icons.shield_rounded, - body: Filters(), ), const AppScreen( name: "settings", icon: Icons.settings_rounded, - body: Settings() ) ]; \ No newline at end of file diff --git a/lib/config/sizes.dart b/lib/config/sizes.dart new file mode 100644 index 0000000..0fc2640 --- /dev/null +++ b/lib/config/sizes.dart @@ -0,0 +1 @@ +const double desktopBreakpoint = 1000; \ No newline at end of file diff --git a/lib/constants/routes_names.dart b/lib/constants/routes_names.dart new file mode 100644 index 0000000..dae8723 --- /dev/null +++ b/lib/constants/routes_names.dart @@ -0,0 +1,19 @@ +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"; +} \ No newline at end of file diff --git a/lib/functions/desktop_mode.dart b/lib/functions/desktop_mode.dart new file mode 100644 index 0000000..4586068 --- /dev/null +++ b/lib/functions/desktop_mode.dart @@ -0,0 +1,5 @@ +import 'package:adguard_home_manager/config/sizes.dart'; + +bool isDesktop(double width) { + return width > desktopBreakpoint; +} \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ee0533d..d4962e3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -667,5 +667,7 @@ "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." + "showTopItemsChartDescription": "Shows by default the ring chart on the top items sections. Only affects to the mobile view.", + "openMenu": "Open menu", + "closeMenu": "Close menu" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 4276b7b..2f202a8 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -667,5 +667,7 @@ "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." + "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ú" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 98bb878..fafb46e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,8 +15,7 @@ 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/routes/router.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; @@ -204,7 +203,7 @@ class _MainState extends State
{ final appConfigProvider = Provider.of(context); return DynamicColorBuilder( - builder: (lightDynamic, darkDynamic) => MaterialApp( + builder: (lightDynamic, darkDynamic) => MaterialApp.router( title: 'AdGuard Home Manager', theme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 ? appConfigProvider.useDynamicColor == true @@ -243,7 +242,7 @@ class _MainState extends State
{ child: child!, ); }, - home: const Base(), + routerConfig: goRouter, ), ); } diff --git a/lib/models/app_screen.dart b/lib/models/app_screen.dart index bce5cc3..1e377eb 100644 --- a/lib/models/app_screen.dart +++ b/lib/models/app_screen.dart @@ -4,14 +4,12 @@ class AppScreen { final String name; final IconData icon; final PreferredSizeWidget? appBar; - final Widget body; final Widget? fab; const AppScreen({ required this.name, required this.icon, this.appBar, - required this.body, this.fab }); } \ No newline at end of file diff --git a/lib/routes/router.dart b/lib/routes/router.dart new file mode 100644 index 0000000..9b1cfba --- /dev/null +++ b/lib/routes/router.dart @@ -0,0 +1,20 @@ +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; + +import 'package:adguard_home_manager/routes/router_globals.dart'; +import 'package:adguard_home_manager/routes/routes.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; +import 'package:adguard_home_manager/constants/routes_names.dart'; + +final goRouter = GoRouter( + navigatorKey: rootNavigatorKey, + redirect: (context, state) { + final serversProvider = Provider.of(context, listen: false); + if (serversProvider.selectedServer == null) { + return RoutesNames.connect; + } + return null; + }, + initialLocation: RoutesNames.home, + routes: routes, +); \ No newline at end of file diff --git a/lib/routes/router_globals.dart b/lib/routes/router_globals.dart new file mode 100644 index 0000000..739c50c --- /dev/null +++ b/lib/routes/router_globals.dart @@ -0,0 +1,10 @@ +import 'package:flutter/widgets.dart'; + +final GlobalKey rootNavigatorKey = GlobalKey(); +final GlobalKey connectNavigatorKey = GlobalKey(); +final GlobalKey homeNavigatorKey = GlobalKey(); +final GlobalKey clientsNavigatorKey = GlobalKey(); +final GlobalKey clientsListNavigatorKey = GlobalKey(); +final GlobalKey logsNavigatorKey = GlobalKey(); +final GlobalKey filtersNavigatorKey = GlobalKey(); +final GlobalKey settingsNavigatorKey = GlobalKey(); \ No newline at end of file diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart new file mode 100644 index 0000000..04bd218 --- /dev/null +++ b/lib/routes/routes.dart @@ -0,0 +1,113 @@ +import 'package:go_router/go_router.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/settings/settings.dart'; +import 'package:adguard_home_manager/screens/clients/client/client_placeholder.dart'; +import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dart'; +import 'package:adguard_home_manager/screens/logs/logs.dart'; +import 'package:adguard_home_manager/widgets/layout.dart'; + +import 'package:adguard_home_manager/routes/router_globals.dart'; +import 'package:adguard_home_manager/constants/routes_names.dart'; + +final List routes = [ + GoRoute( + path: "/", + redirect: (context, state) => RoutesNames.home, + ), + StatefulShellRoute.indexedStack( + builder: (context, state, navigationShell) => Layout( + navigationShell: navigationShell + ), + branches: [ + StatefulShellBranch( + navigatorKey: homeNavigatorKey, + routes: [ + GoRoute( + parentNavigatorKey: homeNavigatorKey, + path: RoutesNames.home, + builder: (context, state) => const Home(), + ), + GoRoute( + parentNavigatorKey: homeNavigatorKey, + path: RoutesNames.queriedDomains, + builder: (context, state) => const Home(), + ), + GoRoute( + parentNavigatorKey: homeNavigatorKey, + path: RoutesNames.blockedDomains, + builder: (context, state) => const Home(), + ), + GoRoute( + parentNavigatorKey: homeNavigatorKey, + path: RoutesNames.recurrentClients, + builder: (context, state) => const Home(), + ), + ] + ), + StatefulShellBranch( + navigatorKey: clientsNavigatorKey, + routes: [ + ShellRoute( + parentNavigatorKey: clientsNavigatorKey, + navigatorKey: clientsListNavigatorKey, + builder: (context, state, child) => Clients(child: child), + routes: [ + GoRoute( + path: RoutesNames.clientPlaceholder, + parentNavigatorKey: clientsListNavigatorKey, + builder: (context, state) => const ClientPlaceholder(), + ), + GoRoute( + path: RoutesNames.client, + parentNavigatorKey: clientsListNavigatorKey, + builder: (context, state) => LogsListClient( + id: (state.extra as Map?)?['id'] + ) + ) + ] + ) + ] + ), + StatefulShellBranch( + navigatorKey: logsNavigatorKey, + routes: [ + GoRoute( + path: RoutesNames.logs, + builder: (context, state) => const Logs(), + ) + ] + ), + StatefulShellBranch( + navigatorKey: filtersNavigatorKey, + routes: [ + GoRoute( + path: RoutesNames.filters, + builder: (context, state) => const Filters(), + ) + ] + ), + StatefulShellBranch( + navigatorKey: settingsNavigatorKey, + routes: [ + GoRoute( + path: RoutesNames.settings, + builder: (context, state) => const Settings(), + ) + ] + ), + StatefulShellBranch( + navigatorKey: connectNavigatorKey, + routes: [ + GoRoute( + path: RoutesNames.connect, + builder: (context, state) => const Connect(), + ) + ] + ), + ] + ) +]; \ No newline at end of file diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index 24b69ae..eea9dd6 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -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'; @@ -157,7 +155,6 @@ class _AddedListState extends State { } return CustomTabContentList( - noSliver: !(Platform.isAndroid || Platform.isIOS), listPadding: widget.splitView == true ? const EdgeInsets.only(top: 8) : null, diff --git a/lib/screens/clients/client/client_placeholder.dart b/lib/screens/clients/client/client_placeholder.dart new file mode 100644 index 0000000..cf4f485 --- /dev/null +++ b/lib/screens/clients/client/client_placeholder.dart @@ -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"), + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/client/logs_list_client.dart b/lib/screens/clients/client/logs_list_client.dart index 0ddc99a..f35de0b 100644 --- a/lib/screens/clients/client/logs_list_client.dart +++ b/lib/screens/clients/client/logs_list_client.dart @@ -1,7 +1,9 @@ import 'dart:io'; +import 'package:adguard_home_manager/constants/enums.dart'; import 'package:flutter/material.dart'; import 'package:async/async.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -12,18 +14,12 @@ 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; +class LogsListClient extends StatefulHookWidget { + final String id; const LogsListClient({ Key? key, - required this.ip, - this.name, - required this.serversProvider, - required this.appConfigProvider + required this.id, }) : super(key: key); @override @@ -38,11 +34,9 @@ class _LogsListClientState extends State { int logsQuantity = 100; int offset = 0; - int loadStatus = 0; + LoadStatus loadStatus = LoadStatus.loading; LogsData? logsData; - String previousIp = ""; - bool showDivider = true; CancelableOperation? cancelableRequest; @@ -67,7 +61,7 @@ class _LogsListClientState extends State { serversProvider.apiClient!.getLogs( count: logsQuantity, offset: offst, - search: '"${widget.ip}"' + search: '"${widget.id}"' ) ); @@ -90,11 +84,11 @@ class _LogsListClientState extends State { LogsData newLogsData = result['data']; setState(() => logsData = newLogsData); } - setState(() => loadStatus = 1); + setState(() => loadStatus = LoadStatus.loaded); } else { - setState(() => loadStatus = 2); - widget.appConfigProvider.addLog(result['log']); + setState(() => loadStatus = LoadStatus.error); + Provider.of(context, listen: false).addLog(result['log']); } } } @@ -116,8 +110,6 @@ class _LogsListClientState extends State { @override void initState() { scrollController = ScrollController()..addListener(scrollListener); - fetchLogs(inOffset: 0); - setState(() => previousIp = widget.ip); super.initState(); } @@ -125,15 +117,15 @@ class _LogsListClientState extends State { Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; - if (widget.ip != previousIp) { - setState(() => loadStatus = 0); + useEffect(() { + setState(() => loadStatus = LoadStatus.loading); fetchLogs(inOffset: 0); - setState(() => previousIp = widget.ip); - } + return null; + }, [widget.id]); Widget status() { switch (loadStatus) { - case 0: + case LoadStatus.loading: return SizedBox( width: double.maxFinite, child: Column( @@ -154,7 +146,7 @@ class _LogsListClientState extends State { ), ); - case 1: + case LoadStatus.loaded: if (logsData!.data.isNotEmpty) { return RefreshIndicator( onRefresh: fetchLogs, @@ -217,7 +209,7 @@ class _LogsListClientState extends State { ); } - case 2: + case LoadStatus.error: return SizedBox( width: double.maxFinite, child: Column( @@ -249,7 +241,7 @@ class _LogsListClientState extends State { return Scaffold( appBar: AppBar( - title: Text(widget.name != null && widget.name != '' ? widget.name! : widget.ip), + title: Text(widget.id), centerTitle: true, actions: [ if (!(Platform.isAndroid || Platform.isIOS)) ...[ diff --git a/lib/screens/clients/clients.dart b/lib/screens/clients/clients.dart index 4762e5b..45ccbd4 100644 --- a/lib/screens/clients/clients.dart +++ b/lib/screens/clients/clients.dart @@ -1,24 +1,25 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; -import 'package:flutter_split_view/flutter_split_view.dart'; +import 'package:go_router/go_router.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/client/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/constants/routes_names.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.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); + final Widget child; + + const Clients({ + Key? key, + required this.child, + }) : super(key: key); @override State createState() => _ClientsState(); @@ -53,9 +54,7 @@ class _ClientsState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { - final serversProvider = Provider.of(context); final clientsProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); final width = MediaQuery.of(context).size.width; @@ -96,163 +95,119 @@ class _ClientsState extends State with TickerProviderStateMixin { 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, + onClientSelected: (client) => context.go( + RoutesNames.client, + extra: { + "id": client.name != null && client.name != "" + ? client.name + : client.ip + } + ), + splitView: isDesktop(width), ), 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, + onClientSelected: (client) => context.go( + RoutesNames.client, + extra: { "id": client.name } + ), + splitView: isDesktop(width), ), ] ); } - 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 Row( + children: [ + SizedBox( + width: isDesktop(width) ? 300 : width, + height: double.maxFinite, + child: Material( + child: 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), + 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, + 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: ClientsDesktopView( - serversProvider: serversProvider, - appConfigProvider: appConfigProvider, + if (isDesktop(width) == true) Expanded( + child: widget.child, ) - ); - } - 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), - 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, - 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) - ) - ); - } - } + ], + ); } } \ No newline at end of file diff --git a/lib/screens/clients/clients_desktop_view.dart b/lib/screens/clients/clients_desktop_view.dart index 9e261ff..7356e15 100644 --- a/lib/screens/clients/clients_desktop_view.dart +++ b/lib/screens/clients/clients_desktop_view.dart @@ -97,18 +97,10 @@ class _ClientsDesktopViewState extends State with TickerPro 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, @@ -117,14 +109,7 @@ class _ClientsDesktopViewState extends State with TickerPro 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, diff --git a/lib/screens/clients/clients_list.dart b/lib/screens/clients/clients_list.dart index f3ee547..d0ecd33 100644 --- a/lib/screens/clients/clients_list.dart +++ b/lib/screens/clients/clients_list.dart @@ -15,7 +15,6 @@ class ClientsList extends StatelessWidget { final void Function(AutoClient) onClientSelected; final AutoClient? selectedClient; final bool splitView; - final bool sliver; const ClientsList({ Key? key, @@ -24,7 +23,6 @@ class ClientsList extends StatelessWidget { required this.onClientSelected, this.selectedClient, required this.splitView, - required this.sliver }) : super(key: key); @override @@ -35,7 +33,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, diff --git a/lib/screens/home/appbar.dart b/lib/screens/home/appbar.dart index 8660f54..34893b6 100644 --- a/lib/screens/home/appbar.dart +++ b/lib/screens/home/appbar.dart @@ -1,3 +1,4 @@ +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -25,6 +26,8 @@ class HomeAppBar extends StatelessWidget { final statusProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + final Server? server = serversProvider.selectedServer; void navigateServers() { @@ -40,6 +43,7 @@ class HomeAppBar extends StatelessWidget { floating: true, centerTitle: false, forceElevated: innerBoxScrolled, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, leading: Stack( children: [ Center( diff --git a/lib/widgets/bottom_nav_bar.dart b/lib/widgets/bottom_nav_bar.dart index 366fe2c..0f16dcd 100644 --- a/lib/widgets/bottom_nav_bar.dart +++ b/lib/widgets/bottom_nav_bar.dart @@ -84,21 +84,21 @@ class BottomNavBar extends StatelessWidget { ), label: translatedName(screen.name) )).toList(), - onDestinationSelected: (value) { - // Reset clients tab to 0 when changing screen - if (value != 1) { - appConfigProvider.setSelectedClientsTab(0); - } - // Reset logs filters when changing screen - if (value != 2) { - logsProvider.resetFilters(); - } - // Reset settings selected screen - if (value != screens.length-1) { - appConfigProvider.setSelectedSettingsScreen(screen: null); - } - appConfigProvider.setSelectedScreen(value); - }, + // onDestinationSelected: (value) { + // // Reset clients tab to 0 when changing screen + // if (value != 1) { + // appConfigProvider.setSelectedClientsTab(0); + // } + // // Reset logs filters when changing screen + // if (value != 2) { + // logsProvider.resetFilters(); + // } + // // Reset settings selected screen + // if (value != screens.length-1) { + // appConfigProvider.setSelectedSettingsScreen(screen: null); + // } + // appConfigProvider.setSelectedScreen(value); + // }, ); } } \ No newline at end of file diff --git a/lib/widgets/layout.dart b/lib/widgets/layout.dart new file mode 100644 index 0000000..3513bb9 --- /dev/null +++ b/lib/widgets/layout.dart @@ -0,0 +1,250 @@ +import 'package:adguard_home_manager/widgets/bottom_nav_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:go_router/go_router.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/config/app_screens.dart'; +import 'package:adguard_home_manager/config/sizes.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; + +class Layout extends StatefulWidget { + final StatefulNavigationShell navigationShell; + + const Layout({ + Key? key, + required this.navigationShell, + }) : super(key: key); + + @override + State createState() => _LayoutState(); +} + +class _LayoutState extends State { + bool _drawerExpanded = true; + + void _goBranch(int index) { + widget.navigationShell.goBranch( + index, + initialLocation: index == widget.navigationShell.currentIndex, + ); + } + + @override + Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + + final serversProvider = Provider.of(context); + + String translatedName(String key) { + switch (key) { + case 'home': + return AppLocalizations.of(context)!.home; + + case 'settings': + return AppLocalizations.of(context)!.settings; + + case 'connect': + return AppLocalizations.of(context)!.connect; + + case 'clients': + return AppLocalizations.of(context)!.clients; + + case 'logs': + return AppLocalizations.of(context)!.logs; + + case 'filters': + return AppLocalizations.of(context)!.filters; + + default: + return ''; + } + } + + if (width > desktopBreakpoint) { + return Material( + child: Row( + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.ease, + width: _drawerExpanded ? 250 : 90, + child: ListView( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 16 + ), + child: IconButton( + onPressed: () => setState(() => _drawerExpanded = !_drawerExpanded), + icon: const Icon(Icons.menu_open_rounded), + tooltip: _drawerExpanded == true + ? AppLocalizations.of(context)!.closeMenu + : AppLocalizations.of(context)!.openMenu, + ), + ), + ], + ), + if (serversProvider.selectedServer != null) + ...screensServerConnected.asMap().entries.map( + (s) => DrawerTile( + icon: s.value.icon, + title: translatedName(s.value.name), + isSelected: + widget.navigationShell.currentIndex == s.key, + onSelect: () => _goBranch(s.key), + withoutTitle: !_drawerExpanded, + ), + ), + if (serversProvider.selectedServer == null) + ...screensSelectServer.asMap().entries.map( + (s) => DrawerTile( + icon: s.value.icon, + title: translatedName(s.value.name), + isSelected: + widget.navigationShell.currentIndex == s.key, + onSelect: () => _goBranch(s.key), + withoutTitle: !_drawerExpanded, + ), + ), + ], + ), + ), + Expanded( + child: widget.navigationShell + ), + ], + ), + ); + } + else { + final screens = serversProvider.selectedServer != null && serversProvider.apiClient != null + ? screensServerConnected + : screensSelectServer; + + return Scaffold( + body: widget.navigationShell, + bottomNavigationBar: NavigationBar( + selectedIndex: (serversProvider.selectedServer == null || serversProvider.apiClient == null) && widget.navigationShell.currentIndex > 1 + ? 0 + : widget.navigationShell.currentIndex, + onDestinationSelected: (s) => _goBranch(s), + destinations: screens.asMap().entries.map((screen) => NavigationDestination( + icon: Stack( + children: [ + Icon( + screen.value.icon, + color: widget.navigationShell.currentIndex == screen.key + ? Theme.of(context).colorScheme.onSecondaryContainer + : Theme.of(context).colorScheme.onSurfaceVariant, + ), + if ( + screen.value.name == 'settings' && + serversProvider.updateAvailable.data != null && + serversProvider.updateAvailable.data!.canAutoupdate == true + ) Positioned( + bottom: 0, + right: -12, + child: Container( + width: 10, + height: 10, + margin: const EdgeInsets.only(right: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.red + ), + ), + ) + ], + ), + label: translatedName(screen.value.name) + )).toList(), + ) + ); + } + } +} + +class DrawerTile extends StatelessWidget { + final IconData icon; + final String title; + final bool isSelected; + final void Function() onSelect; + final bool? withoutTitle; + + const DrawerTile({ + super.key, + required this.icon, + required this.title, + required this.isSelected, + required this.onSelect, + this.withoutTitle, + }); + + @override + Widget build(BuildContext context) { + Widget iconWidget = withoutTitle == true + ? Tooltip( + message: title, + child: Icon( + icon, + color: isSelected + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + : Icon( + icon, + color: isSelected + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurfaceVariant, + ); + + return Padding( + padding: const EdgeInsets.only(right: 16), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(30), + bottomRight: Radius.circular(30), + ), + onTap: onSelect, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: isSelected + ? Theme.of(context).colorScheme.secondaryContainer + : null, + borderRadius: const BorderRadius.only( + topRight: Radius.circular(30), + bottomRight: Radius.circular(30), + ), + ), + child: Row(children: [ + iconWidget, + const SizedBox(width: 16), + Flexible( + child: Text( + title, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + color: isSelected + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ) + ]), + ), + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index c647bf5..a194327 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -222,6 +222,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" + flutter_hooks: + dependency: "direct main" + description: + name: flutter_hooks + sha256: "7c8db779c2d1010aa7f9ea3fbefe8f86524fcb87b69e8b0af31e1a4b55422dec" + url: "https://pub.dev" + source: hosted + version: "0.20.3" flutter_html: dependency: "direct main" description: @@ -310,6 +318,14 @@ packages: description: flutter source: sdk version: "0.0.0" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: e156bc1b2088eb5ece9351bccd48c3e1719a4858eacbd44e59162e98a68205d1 + url: "https://pub.dev" + source: hosted + version: "12.0.1" html: dependency: "direct main" description: @@ -382,6 +398,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" markdown: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 2445ec4..06ae67d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,6 +74,8 @@ dependencies: flutter_dotenv: ^5.0.2 flutter_reorderable_list: ^1.3.1 pie_chart: ^5.3.2 + go_router: ^12.0.1 + flutter_hooks: ^0.20.3 dev_dependencies: flutter_test: From 96fe7eb730603ed0bc45b247cd158434cc0f3e5c Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 02:19:00 +0100 Subject: [PATCH 039/177] Implemented new router --- lib/config/app_screens.dart | 7 - lib/constants/routes_names.dart | 12 + lib/routes/router_globals.dart | 4 +- lib/routes/routes.dart | 44 +- .../clients/client/logs_list_client.dart | 49 +- lib/screens/clients/clients.dart | 222 +------ lib/screens/clients/clients_desktop_view.dart | 229 ------- lib/screens/clients/clients_list.dart | 2 - lib/screens/clients/clients_lists.dart | 222 +++++++ lib/screens/filters/filters.dart | 34 +- lib/screens/filters/filters_tabs_view.dart | 4 + .../filters/filters_triple_column.dart | 4 + lib/screens/logs/log_details_screen.dart | 4 + lib/screens/logs/log_tile.dart | 6 +- lib/screens/logs/logs.dart | 601 ++---------------- lib/screens/logs/logs_list.dart | 566 +++++++++++++++++ lib/screens/settings/settings.dart | 65 +- 17 files changed, 985 insertions(+), 1090 deletions(-) delete mode 100644 lib/screens/clients/clients_desktop_view.dart create mode 100644 lib/screens/clients/clients_lists.dart create mode 100644 lib/screens/logs/logs_list.dart diff --git a/lib/config/app_screens.dart b/lib/config/app_screens.dart index 91d33f0..4f9a97d 100644 --- a/lib/config/app_screens.dart +++ b/lib/config/app_screens.dart @@ -1,12 +1,5 @@ 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/settings/settings.dart'; - import 'package:adguard_home_manager/models/app_screen.dart'; List screensSelectServer = [ diff --git a/lib/constants/routes_names.dart b/lib/constants/routes_names.dart index dae8723..fa0b07c 100644 --- a/lib/constants/routes_names.dart +++ b/lib/constants/routes_names.dart @@ -16,4 +16,16 @@ class RoutesNames { 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"; } \ No newline at end of file diff --git a/lib/routes/router_globals.dart b/lib/routes/router_globals.dart index 739c50c..f78c1a7 100644 --- a/lib/routes/router_globals.dart +++ b/lib/routes/router_globals.dart @@ -4,7 +4,7 @@ final GlobalKey rootNavigatorKey = GlobalKey(); final GlobalKey connectNavigatorKey = GlobalKey(); final GlobalKey homeNavigatorKey = GlobalKey(); final GlobalKey clientsNavigatorKey = GlobalKey(); -final GlobalKey clientsListNavigatorKey = GlobalKey(); final GlobalKey logsNavigatorKey = GlobalKey(); final GlobalKey filtersNavigatorKey = GlobalKey(); -final GlobalKey settingsNavigatorKey = GlobalKey(); \ No newline at end of file +final GlobalKey settingsNavigatorKey = GlobalKey(); +final GlobalKey settingsListNavigatorKey = GlobalKey(); \ No newline at end of file diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart index 04bd218..a216446 100644 --- a/lib/routes/routes.dart +++ b/lib/routes/routes.dart @@ -4,10 +4,8 @@ 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/settings/settings.dart'; -import 'package:adguard_home_manager/screens/clients/client/client_placeholder.dart'; -import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dart'; import 'package:adguard_home_manager/screens/logs/logs.dart'; +import 'package:adguard_home_manager/screens/settings/settings.dart'; import 'package:adguard_home_manager/widgets/layout.dart'; import 'package:adguard_home_manager/routes/router_globals.dart'; @@ -31,44 +29,15 @@ final List routes = [ path: RoutesNames.home, builder: (context, state) => const Home(), ), - GoRoute( - parentNavigatorKey: homeNavigatorKey, - path: RoutesNames.queriedDomains, - builder: (context, state) => const Home(), - ), - GoRoute( - parentNavigatorKey: homeNavigatorKey, - path: RoutesNames.blockedDomains, - builder: (context, state) => const Home(), - ), - GoRoute( - parentNavigatorKey: homeNavigatorKey, - path: RoutesNames.recurrentClients, - builder: (context, state) => const Home(), - ), ] ), StatefulShellBranch( navigatorKey: clientsNavigatorKey, routes: [ - ShellRoute( + GoRoute( parentNavigatorKey: clientsNavigatorKey, - navigatorKey: clientsListNavigatorKey, - builder: (context, state, child) => Clients(child: child), - routes: [ - GoRoute( - path: RoutesNames.clientPlaceholder, - parentNavigatorKey: clientsListNavigatorKey, - builder: (context, state) => const ClientPlaceholder(), - ), - GoRoute( - path: RoutesNames.client, - parentNavigatorKey: clientsListNavigatorKey, - builder: (context, state) => LogsListClient( - id: (state.extra as Map?)?['id'] - ) - ) - ] + path: RoutesNames.clients, + builder: (context, state) => const Clients(), ) ] ), @@ -76,6 +45,7 @@ final List routes = [ navigatorKey: logsNavigatorKey, routes: [ GoRoute( + parentNavigatorKey: logsNavigatorKey, path: RoutesNames.logs, builder: (context, state) => const Logs(), ) @@ -85,6 +55,7 @@ final List routes = [ navigatorKey: filtersNavigatorKey, routes: [ GoRoute( + parentNavigatorKey: filtersNavigatorKey, path: RoutesNames.filters, builder: (context, state) => const Filters(), ) @@ -94,7 +65,8 @@ final List routes = [ navigatorKey: settingsNavigatorKey, routes: [ GoRoute( - path: RoutesNames.settings, + parentNavigatorKey: settingsNavigatorKey, + path: RoutesNames.settings, builder: (context, state) => const Settings(), ) ] diff --git a/lib/screens/clients/client/logs_list_client.dart b/lib/screens/clients/client/logs_list_client.dart index f35de0b..eb724fd 100644 --- a/lib/screens/clients/client/logs_list_client.dart +++ b/lib/screens/clients/client/logs_list_client.dart @@ -1,9 +1,7 @@ import 'dart:io'; -import 'package:adguard_home_manager/constants/enums.dart'; import 'package:flutter/material.dart'; import 'package:async/async.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -14,12 +12,20 @@ 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 StatefulHookWidget { - final String id; +class LogsListClient extends StatefulWidget { + final String ip; + final String? name; + final ServersProvider serversProvider; + final AppConfigProvider appConfigProvider; + final bool splitView; const LogsListClient({ Key? key, - required this.id, + required this.ip, + this.name, + required this.serversProvider, + required this.appConfigProvider, + required this.splitView, }) : super(key: key); @override @@ -34,9 +40,11 @@ class _LogsListClientState extends State { int logsQuantity = 100; int offset = 0; - LoadStatus loadStatus = LoadStatus.loading; + int loadStatus = 0; LogsData? logsData; + String previousIp = ""; + bool showDivider = true; CancelableOperation? cancelableRequest; @@ -61,7 +69,7 @@ class _LogsListClientState extends State { serversProvider.apiClient!.getLogs( count: logsQuantity, offset: offst, - search: '"${widget.id}"' + search: '"${widget.ip}"' ) ); @@ -84,11 +92,11 @@ class _LogsListClientState extends State { LogsData newLogsData = result['data']; setState(() => logsData = newLogsData); } - setState(() => loadStatus = LoadStatus.loaded); + setState(() => loadStatus = 1); } else { - setState(() => loadStatus = LoadStatus.error); - Provider.of(context, listen: false).addLog(result['log']); + setState(() => loadStatus = 2); + widget.appConfigProvider.addLog(result['log']); } } } @@ -110,6 +118,8 @@ class _LogsListClientState extends State { @override void initState() { scrollController = ScrollController()..addListener(scrollListener); + fetchLogs(inOffset: 0); + setState(() => previousIp = widget.ip); super.initState(); } @@ -117,15 +127,15 @@ class _LogsListClientState extends State { Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; - useEffect(() { - setState(() => loadStatus = LoadStatus.loading); + if (widget.ip != previousIp) { + setState(() => loadStatus = 0); fetchLogs(inOffset: 0); - return null; - }, [widget.id]); + setState(() => previousIp = widget.ip); + } Widget status() { switch (loadStatus) { - case LoadStatus.loading: + case 0: return SizedBox( width: double.maxFinite, child: Column( @@ -146,7 +156,7 @@ class _LogsListClientState extends State { ), ); - case LoadStatus.loaded: + case 1: if (logsData!.data.isNotEmpty) { return RefreshIndicator( onRefresh: fetchLogs, @@ -189,7 +199,8 @@ class _LogsListClientState extends State { ) )) } - } + }, + twoColumns: widget.splitView, ); } } @@ -209,7 +220,7 @@ class _LogsListClientState extends State { ); } - case LoadStatus.error: + case 2: return SizedBox( width: double.maxFinite, child: Column( @@ -241,7 +252,7 @@ class _LogsListClientState extends State { return Scaffold( appBar: AppBar( - title: Text(widget.id), + title: Text(widget.name != null && widget.name != '' ? widget.name! : widget.ip), centerTitle: true, actions: [ if (!(Platform.isAndroid || Platform.isIOS)) ...[ diff --git a/lib/screens/clients/clients.dart b/lib/screens/clients/clients.dart index 45ccbd4..80393a8 100644 --- a/lib/screens/clients/clients.dart +++ b/lib/screens/clients/clients.dart @@ -1,213 +1,55 @@ import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:provider/provider.dart'; +import 'package:flutter_split_view/flutter_split_view.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/added_list.dart'; +import 'package:adguard_home_manager/screens/clients/clients_lists.dart'; -import 'package:adguard_home_manager/constants/routes_names.dart'; -import 'package:adguard_home_manager/functions/desktop_mode.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'; class Clients extends StatefulWidget { - final Widget child; - - const Clients({ - Key? key, - required this.child, - }) : super(key: key); + const Clients({Key? key}) : super(key: key); @override State createState() => _ClientsState(); } class _ClientsState extends State with TickerProviderStateMixin { - late TabController tabController; - final ScrollController scrollController = ScrollController(); - - bool searchMode = false; - final TextEditingController searchController = TextEditingController(); - - @override - void initState() { - final clientsProvider = Provider.of(context, listen: false); - clientsProvider.fetchClients(updateLoading: true); - - super.initState(); - tabController = TabController( - initialIndex: 0, - length: 2, - vsync: this, - ); - tabController.addListener( - () => Provider.of(context, listen: false).setSelectedClientsTab(tabController.index) - ); - } - List generateClientsList(List clients, List ips) { return clients.where((client) => ips.contains(client.ip)).toList(); } @override Widget build(BuildContext context) { - final clientsProvider = Provider.of(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) - ], + return 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 + ), + ), + ), ), - ), - 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) => context.go( - RoutesNames.client, - extra: { - "id": client.name != null && client.name != "" - ? client.name - : client.ip - } - ), - splitView: isDesktop(width), - ), - AddedList( - scrollController: scrollController, - data: clientsProvider.loadStatus == LoadStatus.loaded - ? clientsProvider.filteredAddedClients : [], - onClientSelected: (client) => context.go( - RoutesNames.client, - extra: { "id": client.name } - ), - splitView: isDesktop(width), - ), - ] - ); - } - - return Row( - children: [ - SizedBox( - width: isDesktop(width) ? 300 : width, - height: double.maxFinite, - child: Material( - child: 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), - 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, - 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) - ) - ), - ), - ), - if (isDesktop(width) == true) Expanded( - child: widget.child, - ) - ], + child: const ClientsLists( + splitView: true, + ) + ); + } + else { + return const ClientsLists( + splitView: false, + ); + } + }, ); } } \ No newline at end of file diff --git a/lib/screens/clients/clients_desktop_view.dart b/lib/screens/clients/clients_desktop_view.dart deleted file mode 100644 index 7356e15..0000000 --- a/lib/screens/clients/clients_desktop_view.dart +++ /dev/null @@ -1,229 +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/client/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 createState() => _ClientsDesktopViewState(); -} - -class _ClientsDesktopViewState extends State 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(context); - final clientsProvider = Provider.of(context); - final appConfigProvider = Provider.of(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; - - }), - selectedClient: selectedActiveClient, - splitView: true, - ), - AddedList( - scrollController: scrollController, - data: clientsProvider.loadStatus == LoadStatus.loaded - ? clientsProvider.filteredAddedClients : [], - onClientSelected: (client) => setState(() { - selectedActiveClient = null; - selectedAddedClient = client; - - }), - 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) - ) - ); - } - } -} \ No newline at end of file diff --git a/lib/screens/clients/clients_list.dart b/lib/screens/clients/clients_list.dart index d0ecd33..2e52738 100644 --- a/lib/screens/clients/clients_list.dart +++ b/lib/screens/clients/clients_list.dart @@ -10,7 +10,6 @@ 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 data; final void Function(AutoClient) onClientSelected; final AutoClient? selectedClient; @@ -18,7 +17,6 @@ class ClientsList extends StatelessWidget { const ClientsList({ Key? key, - required this.scrollController, required this.data, required this.onClientSelected, this.selectedClient, diff --git a/lib/screens/clients/clients_lists.dart b/lib/screens/clients/clients_lists.dart new file mode 100644 index 0000000..ec451bc --- /dev/null +++ b/lib/screens/clients/clients_lists.dart @@ -0,0 +1,222 @@ +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({ + Key? key, + required this.splitView, + }) : super(key: key); + + @override + State createState() => _ClientsListsState(); +} + +class _ClientsListsState extends State 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(context, listen: false); + clientsProvider.fetchClients(updateLoading: true); + + super.initState(); + tabController = TabController( + initialIndex: 0, + length: 2, + vsync: this, + ); + tabController.addListener( + () => Provider.of(context, listen: false).setSelectedClientsTab(tabController.index) + ); + } + + + @override + Widget build(BuildContext context) { + final serversProvider = Provider.of(context); + final clientsProvider = Provider.of(context); + final appConfigProvider = Provider.of(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.push(context, 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.push(context, 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, + ), + ] + ) + ) + ); + } +} \ No newline at end of file diff --git a/lib/screens/filters/filters.dart b/lib/screens/filters/filters.dart index c4e6d9b..4b94b1e 100644 --- a/lib/screens/filters/filters.dart +++ b/lib/screens/filters/filters.dart @@ -324,20 +324,24 @@ class _FiltersState extends State { } } - 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, + ); + } + }, + ); } } \ No newline at end of file diff --git a/lib/screens/filters/filters_tabs_view.dart b/lib/screens/filters/filters_tabs_view.dart index 81cd67a..63e6a4e 100644 --- a/lib/screens/filters/filters_tabs_view.dart +++ b/lib/screens/filters/filters_tabs_view.dart @@ -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'; @@ -47,6 +48,8 @@ class _FiltersTabsViewState extends State with TickerProviderSt Widget build(BuildContext context) { final filteringProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + return DefaultTabController( length: 3, child: NestedScrollView( @@ -62,6 +65,7 @@ class _FiltersTabsViewState extends State with TickerProviderSt forceElevated: innerBoxIsScrolled, centerTitle: false, actions: widget.actions, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, bottom: TabBar( controller: tabController, isScrollable: true, diff --git a/lib/screens/filters/filters_triple_column.dart b/lib/screens/filters/filters_triple_column.dart index 2f7979d..3a8bfa2 100644 --- a/lib/screens/filters/filters_triple_column.dart +++ b/lib/screens/filters/filters_triple_column.dart @@ -13,6 +13,7 @@ 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'; @@ -37,6 +38,8 @@ class FiltersTripleColumn extends StatelessWidget { Widget build(BuildContext context) { final filteringProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + + final width = MediaQuery.of(context).size.width; Widget? generateSubtitle(String rule) { final allowRegex = RegExp(r'^@@.*$'); @@ -325,6 +328,7 @@ class FiltersTripleColumn extends StatelessWidget { return Scaffold( appBar: AppBar( + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, title: Text(AppLocalizations.of(context)!.filters), actions: [ IconButton( diff --git a/lib/screens/logs/log_details_screen.dart b/lib/screens/logs/log_details_screen.dart index 859d5e7..161e9fa 100644 --- a/lib/screens/logs/log_details_screen.dart +++ b/lib/screens/logs/log_details_screen.dart @@ -8,6 +8,7 @@ import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/screens/logs/log_list_tile.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/open_url.dart'; import 'package:adguard_home_manager/constants/urls.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -34,6 +35,8 @@ class LogDetailsScreen extends StatelessWidget { final appConfigProvider = Provider.of(context); final statusProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + Filter? getList(int id) { try { return statusProvider.filteringStatus!.filters.firstWhere((filter) => filter.id == id, orElse: () { @@ -288,6 +291,7 @@ class LogDetailsScreen extends StatelessWidget { floating: true, centerTitle: false, forceElevated: innerBoxIsScrolled, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, title: Text(AppLocalizations.of(context)!.logDetails), actions: [ IconButton( diff --git a/lib/screens/logs/log_tile.dart b/lib/screens/logs/log_tile.dart index bd2b1a9..40f3b0d 100644 --- a/lib/screens/logs/log_tile.dart +++ b/lib/screens/logs/log_tile.dart @@ -17,6 +17,7 @@ class LogTile extends StatelessWidget { final bool? isLogSelected; final void Function(Log) onLogTap; final bool? useAlwaysNormalTile; + final bool twoColumns; const LogTile({ Key? key, @@ -25,7 +26,8 @@ class LogTile extends StatelessWidget { required this.index, this.isLogSelected, required this.onLogTap, - this.useAlwaysNormalTile + this.useAlwaysNormalTile, + required this.twoColumns, }) : super(key: key); @override @@ -83,7 +85,7 @@ class LogTile extends StatelessWidget { } } - if (width > 1100 && !(useAlwaysNormalTile == true)) { + if (twoColumns && !(useAlwaysNormalTile == true)) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: InkWell( diff --git a/lib/screens/logs/logs.dart b/lib/screens/logs/logs.dart index 6d9cef6..b218ad7 100644 --- a/lib/screens/logs/logs.dart +++ b/lib/screens/logs/logs.dart @@ -1,27 +1,10 @@ // 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/logs/logs_filters_modal.dart'; -import 'package:adguard_home_manager/screens/logs/logs_config_modal.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/functions/snackbar.dart'; -import 'package:adguard_home_manager/classes/process_modal.dart'; -import 'package:adguard_home_manager/models/applied_filters.dart'; -import 'package:adguard_home_manager/functions/compare_versions.dart'; -import 'package:adguard_home_manager/providers/clients_provider.dart'; -import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/providers/status_provider.dart'; -import 'package:adguard_home_manager/providers/logs_provider.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/models/logs.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; +import 'package:flutter/material.dart'; + +import 'package:adguard_home_manager/screens/logs/logs_list.dart'; +import 'package:adguard_home_manager/screens/logs/log_details_screen.dart'; class Logs extends StatefulWidget { const Logs({Key? key}) : super(key: key); @@ -31,554 +14,48 @@ class Logs extends StatefulWidget { } class _LogsState extends State { - bool showDivider = true; - - Log? selectedLog; - - void fetchFilteringRules() async { - final appConfigProvider = Provider.of(context, listen: false); - final statusProvider = Provider.of(context, listen: false); - - final result = await statusProvider.getFilteringRules(); - if (mounted && result == false) { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.couldntGetFilteringStatus, - color: Colors.red - ); - } - } - - Future fetchClients() async { - final clientsProvider = Provider.of(context, listen: false); - final appConfigProvider = Provider.of(context, listen: false); - - final result = await clientsProvider.fetchClients(); - if (mounted && result == false) { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.couldntGetFilteringStatus, - color: Colors.red - ); - } - } - - bool scrollListener(ScrollUpdateNotification scrollNotification) { - final logsProvider = Provider.of(context, listen: false); - - if (scrollNotification.metrics.extentAfter < 500 && logsProvider.isLoadingMore == false) { - logsProvider.fetchLogs(loadingMore: true); - } - if (scrollNotification.metrics.pixels > 0) { - setState(() => showDivider = false); - } - else { - setState(() => showDivider = true); - } - - return false; - } - - @override - void initState() { - final logsProvider = Provider.of(context, listen: false); - - logsProvider.fetchLogs(inOffset: 0); - fetchFilteringRules(); - fetchClients(); - super.initState(); - } + Log? _selectedLog; @override Widget build(BuildContext context) { - final serversProvider = Provider.of(context); - final statusProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - final logsProvider = Provider.of(context); + - final width = MediaQuery.of(context).size.width; - - void updateConfig(Map data) async { - ProcessModal processModal = ProcessModal(context: context); - processModal.open(AppLocalizations.of(context)!.updatingSettings); - - final result = serverVersionIsAhead( - currentVersion: statusProvider.serverStatus!.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == true - ? await serversProvider.apiClient!.updateQueryLogParameters(data: data) - : await serversProvider.apiClient!.updateQueryLogParametersLegacy(data: data); - - processModal.close(); - - if (result['result'] == 'success') { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsConfigUpdated, - color: Colors.green - ); - } - else { - appConfigProvider.addLog(result['log']); - - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsConfigNotUpdated, - color: Colors.red - ); - } - } - - void clearQueries() async { - ProcessModal processModal = ProcessModal(context: context); - processModal.open(AppLocalizations.of(context)!.updatingSettings); - - final result = await serversProvider.apiClient!.clearLogs(); - - processModal.close(); - - if (result['result'] == 'success') { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsCleared, - color: Colors.green - ); - } - else { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsNotCleared, - color: Colors.red - ); - } - } - - - void openFilersModal() { - if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => const LogsFiltersModal( - dialog: true, - ), - barrierDismissible: false - ); - } - else { - showModalBottomSheet( - context: context, - builder: (context) => const LogsFiltersModal( - dialog: false, - ), - backgroundColor: Colors.transparent, - isScrollControlled: true - ); - } - } - - final Map translatedString = { - "all": AppLocalizations.of(context)!.all, - "filtered": AppLocalizations.of(context)!.filtered, - "processed": AppLocalizations.of(context)!.processedRow, - "whitelisted": AppLocalizations.of(context)!.processedWhitelistRow, - "blocked": AppLocalizations.of(context)!.blocked, - "blocked_safebrowsing": AppLocalizations.of(context)!.blockedSafeBrowsingRow, - "blocked_parental": AppLocalizations.of(context)!.blockedParentalRow, - "safe_search": AppLocalizations.of(context)!.safeSearch, - }; - - Widget generateBody() { - switch (logsProvider.loadStatus) { - case LoadStatus.loading: - return SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - SliverFillRemaining( - child: SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingLogs, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ) + return LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth > 1000) { + return Material( + color: Colors.transparent, + child: Row( + children: [ + Expanded( + flex: 2, + child: LogsListWidget( + twoColumns: true, + selectedLog: _selectedLog, + onLogSelected: (log) => setState(() => _selectedLog = log), ) - ], - ), - ) - ); - - case LoadStatus.loaded: - return SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => RefreshIndicator( - onRefresh: () async { - await logsProvider.fetchLogs(inOffset: 0); - }, - displacement: 95, - child: NotificationListener( - onNotification: scrollListener, - child: CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - if (logsProvider.logsData!.data.isNotEmpty) SliverList.builder( - itemCount: logsProvider.isLoadingMore - ? logsProvider.logsData!.data.length + 1 - : logsProvider.logsData!.data.length, - itemBuilder: (context, index) { - if (logsProvider.isLoadingMore == true && index == logsProvider.logsData!.data.length) { - return const Padding( - padding: EdgeInsets.symmetric(vertical: 20), - child: Center( - child: CircularProgressIndicator(), - ), - ); - } - else if (logsProvider.logsData!.data[index].question.name != null) { - return LogTile( - log: logsProvider.logsData!.data[index], - index: index, - length: logsProvider.logsData!.data.length, - isLogSelected: selectedLog != null && selectedLog == logsProvider.logsData!.data[index], - onLogTap: (log) { - if (width <= 1100) { - Navigator.push(context, MaterialPageRoute( - builder: (context) => LogDetailsScreen( - log: log, - dialog: false, - ) - )); - } - setState(() => selectedLog = log); - } - ); - } - else { - return null; - } - } - ), - if (logsProvider.logsData!.data.isEmpty) SliverFillRemaining( - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - AppLocalizations.of(context)!.noLogsDisplay, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - if (logsProvider.logsOlderThan != null) Padding( - padding: const EdgeInsets.only( - top: 30, - left: 20, - right: 20 - ), - child: Text( - AppLocalizations.of(context)!.noLogsThatOld, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - ] - ), - ), - ) - ], - ), ), - ), - ) - ); - - case LoadStatus.error: - return SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - SliverFillRemaining( - 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)!.logsNotLoaded, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ) - ) - ], - ), - ) - ); - - default: - return const SizedBox(); - } - } - - Widget logsScreen() { - return Scaffold( - body: NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar.large( - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - title: Text(AppLocalizations.of(context)!.logs), - expandedHeight: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients != null - ? 170 : null, - actions: [ - if (!(Platform.isAndroid || Platform.isIOS)) IconButton( - onPressed: () => logsProvider.fetchLogs(inOffset: 0), - icon: const Icon(Icons.refresh_rounded), - tooltip: AppLocalizations.of(context)!.refresh, - ), - logsProvider.loadStatus == LoadStatus.loaded - ? IconButton( - onPressed: openFilersModal, - icon: const Icon(Icons.filter_list_rounded), - tooltip: AppLocalizations.of(context)!.filters, + Expanded( + flex: 3, + child: _selectedLog != null + ? LogDetailsScreen( + log: _selectedLog!, + dialog: false, ) - : const SizedBox(), - if (statusProvider.serverStatus != null) IconButton( - tooltip: AppLocalizations.of(context)!.settings, - onPressed: () => { - if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => LogsConfigModal( - onConfirm: updateConfig, - onClear: clearQueries, - dialog: true, - serverVersion: statusProvider.serverStatus!.serverVersion, - ), - barrierDismissible: false - ) - } - else { - showModalBottomSheet( - context: context, - builder: (context) => LogsConfigModal( - onConfirm: updateConfig, - onClear: clearQueries, - dialog: false, - serverVersion: statusProvider.serverStatus!.serverVersion, - ), - backgroundColor: Colors.transparent, - isScrollControlled: true - ) - } - }, - icon: const Icon(Icons.settings) - ), - const SizedBox(width: 5), - ], - bottom: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients != null - ? PreferredSize( - preferredSize: const Size(double.maxFinite, 70), - child: Container( - height: 50, - width: double.maxFinite, - padding: const EdgeInsets.only(bottom: 10), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: showDivider == true - ? Theme.of(context).colorScheme.onSurface.withOpacity(0.1) - : Colors.transparent, - ) - ) - ), - child: ListView( - scrollDirection: Axis.horizontal, - children: [ - if (logsProvider.appliedFilters.searchText != null) ...[ - const SizedBox(width: 15), - Chip( - avatar: const Icon( - Icons.search_rounded, - ), - label: Row( - children: [ - Text( - logsProvider.appliedFilters.searchText!, - ), - ], - ), - deleteIcon: const Icon( - Icons.clear, - size: 18, - ), - onDeleted: () { - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus, - searchText: null, - clients: logsProvider.appliedFilters.clients - ) - ); - logsProvider.setSearchText(null); - logsProvider.fetchLogs( - inOffset: 0, - searchText: '' - ); - }, - ), - ], - if (logsProvider.appliedFilters.selectedResultStatus != 'all') ...[ - const SizedBox(width: 15), - Chip( - avatar: const Icon( - Icons.shield_rounded, - ), - label: Row( - children: [ - Text( - translatedString[logsProvider.appliedFilters.selectedResultStatus]!, - ), - ], - ), - deleteIcon: const Icon( - Icons.clear, - size: 18, - ), - onDeleted: () { - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: 'all', - searchText: logsProvider.appliedFilters.searchText, - clients: logsProvider.appliedFilters.clients - ) - ); - logsProvider.setSelectedResultStatus('all'); - logsProvider.fetchLogs( - inOffset: 0, - responseStatus: 'all' - ); - }, - ), - ], - if (logsProvider.appliedFilters.clients != null) ...[ - const SizedBox(width: 15), - Chip( - avatar: const Icon( - Icons.smartphone_rounded, - ), - label: Row( - children: [ - Text( - logsProvider.appliedFilters.clients!.length == 1 - ? logsProvider.appliedFilters.clients![0] - : "${logsProvider.appliedFilters.clients!.length} ${AppLocalizations.of(context)!.clients}", - ), - ], - ), - deleteIcon: const Icon( - Icons.clear, - size: 18, - ), - onDeleted: () { - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus, - searchText: logsProvider.appliedFilters.searchText, - clients: null - ) - ); - logsProvider.setSelectedClients(null); - logsProvider.fetchLogs( - inOffset: 0, - responseStatus: logsProvider.appliedFilters.selectedResultStatus - ); - }, - ), - ], - const SizedBox(width: 15), - ], - ), - ) - ) - : null, - ), - ) - ], - body: generateBody() - ), - ); - } - - if (width > 1100) { - return Material( - color: Colors.transparent, - child: Row( - children: [ - Expanded( - flex: 1, - child: logsScreen() + : const SizedBox() + ) + ], ), - Expanded( - flex: 2, - child: selectedLog != null - ? LogDetailsScreen( - log: selectedLog!, - dialog: false, - ) - : const SizedBox() - ) - ], - ), - ); - } - else { - return logsScreen(); - } + ); + } + else { + return LogsListWidget( + twoColumns: false, + selectedLog: _selectedLog, + onLogSelected: (log) => setState(() => _selectedLog = log), + ); + } + }, + ); } } \ No newline at end of file diff --git a/lib/screens/logs/logs_list.dart b/lib/screens/logs/logs_list.dart new file mode 100644 index 0000000..9ca811d --- /dev/null +++ b/lib/screens/logs/logs_list.dart @@ -0,0 +1,566 @@ +// 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/logs/log_details_screen.dart'; +import 'package:adguard_home_manager/screens/logs/log_tile.dart'; +import 'package:adguard_home_manager/screens/logs/logs_config_modal.dart'; +import 'package:adguard_home_manager/screens/logs/logs_filters_modal.dart'; + +import 'package:adguard_home_manager/classes/process_modal.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/functions/compare_versions.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; +import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/models/applied_filters.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/clients_provider.dart'; +import 'package:adguard_home_manager/providers/logs_provider.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; + +class LogsListWidget extends StatefulWidget { + final Log? selectedLog; + final bool twoColumns; + final void Function(Log) onLogSelected; + + const LogsListWidget({ + Key? key, + required this.twoColumns, + required this.selectedLog, + required this.onLogSelected, + }) : super(key: key); + + @override + State createState() => _LogsListWidgetState(); +} + +class _LogsListWidgetState extends State { + bool showDivider = true; + + void fetchFilteringRules() async { + final appConfigProvider = Provider.of(context, listen: false); + final statusProvider = Provider.of(context, listen: false); + + final result = await statusProvider.getFilteringRules(); + if (mounted && result == false) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.couldntGetFilteringStatus, + color: Colors.red + ); + } + } + + Future fetchClients() async { + final clientsProvider = Provider.of(context, listen: false); + final appConfigProvider = Provider.of(context, listen: false); + + final result = await clientsProvider.fetchClients(); + if (mounted && result == false) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.couldntGetFilteringStatus, + color: Colors.red + ); + } + } + + bool scrollListener(ScrollUpdateNotification scrollNotification) { + final logsProvider = Provider.of(context, listen: false); + + if (scrollNotification.metrics.extentAfter < 500 && logsProvider.isLoadingMore == false) { + logsProvider.fetchLogs(loadingMore: true); + } + if (scrollNotification.metrics.pixels > 0) { + setState(() => showDivider = false); + } + else { + setState(() => showDivider = true); + } + + return false; + } + + @override + void initState() { + final logsProvider = Provider.of(context, listen: false); + + logsProvider.fetchLogs(inOffset: 0); + fetchFilteringRules(); + fetchClients(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final serversProvider = Provider.of(context); + final statusProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); + final logsProvider = Provider.of(context); + + final width = MediaQuery.of(context).size.width; + + void updateConfig(Map data) async { + ProcessModal processModal = ProcessModal(context: context); + processModal.open(AppLocalizations.of(context)!.updatingSettings); + + final result = serverVersionIsAhead( + currentVersion: statusProvider.serverStatus!.serverVersion, + referenceVersion: 'v0.107.28', + referenceVersionBeta: 'v0.108.0-b.33' + ) == true + ? await serversProvider.apiClient!.updateQueryLogParameters(data: data) + : await serversProvider.apiClient!.updateQueryLogParametersLegacy(data: data); + + processModal.close(); + + if (result['result'] == 'success') { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsConfigUpdated, + color: Colors.green + ); + } + else { + appConfigProvider.addLog(result['log']); + + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsConfigNotUpdated, + color: Colors.red + ); + } + } + + void clearQueries() async { + ProcessModal processModal = ProcessModal(context: context); + processModal.open(AppLocalizations.of(context)!.updatingSettings); + + final result = await serversProvider.apiClient!.clearLogs(); + + processModal.close(); + + if (result['result'] == 'success') { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsCleared, + color: Colors.green + ); + } + else { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsNotCleared, + color: Colors.red + ); + } + } + + + void openFilersModal() { + if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (context) => const LogsFiltersModal( + dialog: true, + ), + barrierDismissible: false + ); + } + else { + showModalBottomSheet( + context: context, + builder: (context) => const LogsFiltersModal( + dialog: false, + ), + backgroundColor: Colors.transparent, + isScrollControlled: true + ); + } + } + + final Map translatedString = { + "all": AppLocalizations.of(context)!.all, + "filtered": AppLocalizations.of(context)!.filtered, + "processed": AppLocalizations.of(context)!.processedRow, + "whitelisted": AppLocalizations.of(context)!.processedWhitelistRow, + "blocked": AppLocalizations.of(context)!.blocked, + "blocked_safebrowsing": AppLocalizations.of(context)!.blockedSafeBrowsingRow, + "blocked_parental": AppLocalizations.of(context)!.blockedParentalRow, + "safe_search": AppLocalizations.of(context)!.safeSearch, + }; + + return Scaffold( + body: NestedScrollView( + headerSliverBuilder: (context, innerBoxIsScrolled) => [ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: SliverAppBar.large( + pinned: true, + floating: true, + centerTitle: false, + forceElevated: innerBoxIsScrolled, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, + title: Text(AppLocalizations.of(context)!.logs), + expandedHeight: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients != null + ? 170 : null, + actions: [ + if (!(Platform.isAndroid || Platform.isIOS)) IconButton( + onPressed: () => logsProvider.fetchLogs(inOffset: 0), + icon: const Icon(Icons.refresh_rounded), + tooltip: AppLocalizations.of(context)!.refresh, + ), + logsProvider.loadStatus == LoadStatus.loaded + ? IconButton( + onPressed: openFilersModal, + icon: const Icon(Icons.filter_list_rounded), + tooltip: AppLocalizations.of(context)!.filters, + ) + : const SizedBox(), + if (statusProvider.serverStatus != null) IconButton( + tooltip: AppLocalizations.of(context)!.settings, + onPressed: () => { + if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (context) => LogsConfigModal( + onConfirm: updateConfig, + onClear: clearQueries, + dialog: true, + serverVersion: statusProvider.serverStatus!.serverVersion, + ), + barrierDismissible: false + ) + } + else { + showModalBottomSheet( + context: context, + builder: (context) => LogsConfigModal( + onConfirm: updateConfig, + onClear: clearQueries, + dialog: false, + serverVersion: statusProvider.serverStatus!.serverVersion, + ), + backgroundColor: Colors.transparent, + isScrollControlled: true + ) + } + }, + icon: const Icon(Icons.settings) + ), + const SizedBox(width: 5), + ], + bottom: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients != null + ? PreferredSize( + preferredSize: const Size(double.maxFinite, 70), + child: Container( + height: 50, + width: double.maxFinite, + padding: const EdgeInsets.only(bottom: 10), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: showDivider == true + ? Theme.of(context).colorScheme.onSurface.withOpacity(0.1) + : Colors.transparent, + ) + ) + ), + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + if (logsProvider.appliedFilters.searchText != null) ...[ + const SizedBox(width: 15), + Chip( + avatar: const Icon( + Icons.search_rounded, + ), + label: Row( + children: [ + Text( + logsProvider.appliedFilters.searchText!, + ), + ], + ), + deleteIcon: const Icon( + Icons.clear, + size: 18, + ), + onDeleted: () { + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus, + searchText: null, + clients: logsProvider.appliedFilters.clients + ) + ); + logsProvider.setSearchText(null); + logsProvider.fetchLogs( + inOffset: 0, + searchText: '' + ); + }, + ), + ], + if (logsProvider.appliedFilters.selectedResultStatus != 'all') ...[ + const SizedBox(width: 15), + Chip( + avatar: const Icon( + Icons.shield_rounded, + ), + label: Row( + children: [ + Text( + translatedString[logsProvider.appliedFilters.selectedResultStatus]!, + ), + ], + ), + deleteIcon: const Icon( + Icons.clear, + size: 18, + ), + onDeleted: () { + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: 'all', + searchText: logsProvider.appliedFilters.searchText, + clients: logsProvider.appliedFilters.clients + ) + ); + logsProvider.setSelectedResultStatus('all'); + logsProvider.fetchLogs( + inOffset: 0, + responseStatus: 'all' + ); + }, + ), + ], + if (logsProvider.appliedFilters.clients != null) ...[ + const SizedBox(width: 15), + Chip( + avatar: const Icon( + Icons.smartphone_rounded, + ), + label: Row( + children: [ + Text( + logsProvider.appliedFilters.clients!.length == 1 + ? logsProvider.appliedFilters.clients![0] + : "${logsProvider.appliedFilters.clients!.length} ${AppLocalizations.of(context)!.clients}", + ), + ], + ), + deleteIcon: const Icon( + Icons.clear, + size: 18, + ), + onDeleted: () { + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus, + searchText: logsProvider.appliedFilters.searchText, + clients: null + ) + ); + logsProvider.setSelectedClients(null); + logsProvider.fetchLogs( + inOffset: 0, + responseStatus: logsProvider.appliedFilters.selectedResultStatus + ); + }, + ), + ], + const SizedBox(width: 15), + ], + ), + ) + ) + : null, + ), + ) + ], + body: Builder( + builder: (context) { + switch (logsProvider.loadStatus) { + case LoadStatus.loading: + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) => CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + SliverFillRemaining( + child: SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingLogs, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ) + ) + ], + ), + ) + ); + + case LoadStatus.loaded: + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) => RefreshIndicator( + onRefresh: () async { + await logsProvider.fetchLogs(inOffset: 0); + }, + displacement: 95, + child: NotificationListener( + onNotification: scrollListener, + child: CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + if (logsProvider.logsData!.data.isNotEmpty) SliverList.builder( + itemCount: logsProvider.isLoadingMore + ? logsProvider.logsData!.data.length + 1 + : logsProvider.logsData!.data.length, + itemBuilder: (context, index) { + if (logsProvider.isLoadingMore == true && index == logsProvider.logsData!.data.length) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Center( + child: CircularProgressIndicator(), + ), + ); + } + else if (logsProvider.logsData!.data[index].question.name != null) { + return LogTile( + log: logsProvider.logsData!.data[index], + index: index, + length: logsProvider.logsData!.data.length, + isLogSelected: widget.selectedLog != null && widget.selectedLog == logsProvider.logsData!.data[index], + onLogTap: (log) { + if (!widget.twoColumns) { + Navigator.push(context, MaterialPageRoute( + builder: (context) => LogDetailsScreen( + log: log, + dialog: false, + ) + )); + } + widget.onLogSelected(log); + }, + twoColumns: widget.twoColumns, + ); + } + else { + return null; + } + } + ), + if (logsProvider.logsData!.data.isEmpty) SliverFillRemaining( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + AppLocalizations.of(context)!.noLogsDisplay, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + if (logsProvider.logsOlderThan != null) Padding( + padding: const EdgeInsets.only( + top: 30, + left: 20, + right: 20 + ), + child: Text( + AppLocalizations.of(context)!.noLogsThatOld, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ), + ] + ), + ), + ) + ], + ), + ), + ), + ) + ); + + case LoadStatus.error: + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) => CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + SliverFillRemaining( + 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)!.logsNotLoaded, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ) + ) + ], + ), + ) + ); + + default: + return const SizedBox(); + } + }, + ) + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index 2f9a24d..87d23ef 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -23,6 +23,7 @@ import 'package:adguard_home_manager/widgets/custom_settings_tile.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/functions/desktop_mode.dart'; import 'package:adguard_home_manager/constants/strings.dart'; import 'package:adguard_home_manager/functions/open_url.dart'; import 'package:adguard_home_manager/functions/compare_versions.dart'; @@ -36,35 +37,46 @@ class Settings extends StatelessWidget { @override Widget build(BuildContext context) { - final width = MediaQuery.of(context).size.width; - - 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)!.selectOptionLeftColumn, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurfaceVariant + return LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth > 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)!.selectOptionLeftColumn, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), ), ), - ), - ), - child: const SettingsWidget(), - ); - } - else { - return const SettingsWidget(); - } + child: const SettingsWidget( + twoColumns: true, + ), + ); + } + else { + return const SettingsWidget( + twoColumns: false, + ); + } + }, + ); } } class SettingsWidget extends StatelessWidget { - const SettingsWidget({Key? key}) : super(key: key); + final bool twoColumns; + + const SettingsWidget({ + Key? key, + required this.twoColumns, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -74,7 +86,7 @@ class SettingsWidget extends StatelessWidget { final width = MediaQuery.of(context).size.width; - if (width <= 900 && appConfigProvider.selectedSettingsScreen != null) { + if (!twoColumns && appConfigProvider.selectedSettingsScreen != null) { appConfigProvider.setSelectedSettingsScreen(screen: null); } @@ -86,7 +98,7 @@ class SettingsWidget extends StatelessWidget { required Widget screenToNavigate, required int thisItem }) { - if (width > 900) { + if (twoColumns) { return CustomSettingsTile( title: title, subtitle: subtitle, @@ -125,6 +137,7 @@ class SettingsWidget extends StatelessWidget { floating: true, centerTitle: false, forceElevated: innerBoxIsScrolled, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, title: Text(AppLocalizations.of(context)!.settings), ) ) From 91d4d2c87a911bc1fa9f72826296ef11fe96cada Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 02:47:14 +0100 Subject: [PATCH 040/177] Removed color appbar desktop mode --- .../clients/client/logs_list_client.dart | 240 ++--- lib/screens/filters/add_button.dart | 2 + lib/screens/filters/filters.dart | 2 + lib/screens/filters/list_details_screen.dart | 1 + lib/screens/home/fab.dart | 1 + lib/screens/logs/logs_filters_modal.dart | 2 + lib/screens/logs/logs_list.dart | 4 +- .../access_settings/access_settings.dart | 4 + .../access_settings/clients_list.dart | 1 + lib/screens/settings/advanced_setings.dart | 4 + .../settings/customization/customization.dart | 6 +- lib/screens/settings/dhcp/dhcp.dart | 980 +++++++++--------- lib/screens/settings/dhcp/dhcp_leases.dart | 3 + lib/screens/settings/dns/bootstrap_dns.dart | 4 + lib/screens/settings/dns/cache_config.dart | 4 + lib/screens/settings/dns/dns.dart | 202 ++-- .../settings/dns/dns_server_settings.dart | 3 + .../settings/dns/private_reverse_servers.dart | 4 + lib/screens/settings/dns/upstream_dns.dart | 4 + .../settings/dns_rewrites/dns_rewrites.dart | 364 +++---- .../settings/encryption/encryption.dart | 744 ++++++------- .../general_settings/general_settings.dart | 4 + .../reorderable_top_items_home.dart | 4 + .../settings/safe_search_settings.dart | 322 +++--- .../settings/server_info/server_info.dart | 221 ++-- lib/screens/settings/settings.dart | 4 +- 26 files changed, 1607 insertions(+), 1527 deletions(-) diff --git a/lib/screens/clients/client/logs_list_client.dart b/lib/screens/clients/client/logs_list_client.dart index eb724fd..5d8f6ed 100644 --- a/lib/screens/clients/client/logs_list_client.dart +++ b/lib/screens/clients/client/logs_list_client.dart @@ -8,6 +8,7 @@ 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/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'; @@ -133,127 +134,13 @@ class _LogsListClientState extends State { 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 - ) - )) - } - }, - 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(); - } - } - 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( @@ -265,7 +152,124 @@ class _LogsListClientState extends State { ] ], ), - body: status(), + body: 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.push(context, 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(); + } + }, + ) ); } } \ No newline at end of file diff --git a/lib/screens/filters/add_button.dart b/lib/screens/filters/add_button.dart index 195d7af..7afccf0 100644 --- a/lib/screens/filters/add_button.dart +++ b/lib/screens/filters/add_button.dart @@ -2,6 +2,7 @@ import 'dart:io'; +import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -134,6 +135,7 @@ class AddFiltersButton extends StatelessWidget { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (ctx) => AddListModal( type: type, onConfirm: confirmAddList, diff --git a/lib/screens/filters/filters.dart b/lib/screens/filters/filters.dart index 4b94b1e..5975cf6 100644 --- a/lib/screens/filters/filters.dart +++ b/lib/screens/filters/filters.dart @@ -87,6 +87,7 @@ class _FiltersState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => const CheckHostModal( dialog: false, ), @@ -269,6 +270,7 @@ class _FiltersState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => UpdateIntervalListsModal( interval: filteringProvider.filtering!.interval, onChange: setUpdateFrequency, diff --git a/lib/screens/filters/list_details_screen.dart b/lib/screens/filters/list_details_screen.dart index e98c759..236ee75 100644 --- a/lib/screens/filters/list_details_screen.dart +++ b/lib/screens/filters/list_details_screen.dart @@ -219,6 +219,7 @@ class _ListDetailsScreenState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (ctx) => AddListModal( list: list, type: widget.type, diff --git a/lib/screens/home/fab.dart b/lib/screens/home/fab.dart index 0e7db97..d0701cc 100644 --- a/lib/screens/home/fab.dart +++ b/lib/screens/home/fab.dart @@ -28,6 +28,7 @@ class HomeFab extends StatelessWidget { showModalBottomSheet( context: context, isScrollControlled: true, + useRootNavigator: true, builder: (context) => const ManagementModal( dialog: false, ), diff --git a/lib/screens/logs/logs_filters_modal.dart b/lib/screens/logs/logs_filters_modal.dart index 5251d9a..8f1d7f0 100644 --- a/lib/screens/logs/logs_filters_modal.dart +++ b/lib/screens/logs/logs_filters_modal.dart @@ -89,6 +89,7 @@ class _LogsFiltersModalWidgetState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => FilterStatusModal( value: logsProvider.selectedResultStatus, dialog: false, @@ -113,6 +114,7 @@ class _LogsFiltersModalWidgetState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => ClientsModal( value: logsProvider.selectedClients, dialog: false, diff --git a/lib/screens/logs/logs_list.dart b/lib/screens/logs/logs_list.dart index 9ca811d..6fd80e6 100644 --- a/lib/screens/logs/logs_list.dart +++ b/lib/screens/logs/logs_list.dart @@ -176,6 +176,7 @@ class _LogsListWidgetState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => const LogsFiltersModal( dialog: false, ), @@ -240,7 +241,8 @@ class _LogsListWidgetState extends State { } else { showModalBottomSheet( - context: context, + context: context, + useRootNavigator: true, builder: (context) => LogsConfigModal( onConfirm: updateConfig, onClear: clearQueries, diff --git a/lib/screens/settings/access_settings/access_settings.dart b/lib/screens/settings/access_settings/access_settings.dart index beb34fd..ca9cf12 100644 --- a/lib/screens/settings/access_settings/access_settings.dart +++ b/lib/screens/settings/access_settings/access_settings.dart @@ -6,6 +6,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/settings/access_settings/clients_list.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; @@ -35,6 +36,8 @@ class _AccessSettingsState extends State with TickerProviderStat Widget build(BuildContext context) { final clientsProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + Widget body() { return TabBarView( controller: tabController, @@ -119,6 +122,7 @@ class _AccessSettingsState extends State with TickerProviderStat floating: true, centerTitle: false, forceElevated: innerBoxIsScrolled, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, bottom: tabBar() ), ), diff --git a/lib/screens/settings/access_settings/clients_list.dart b/lib/screens/settings/access_settings/clients_list.dart index 31c01a7..6f87c7c 100644 --- a/lib/screens/settings/access_settings/clients_list.dart +++ b/lib/screens/settings/access_settings/clients_list.dart @@ -361,6 +361,7 @@ class _ClientsListState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => AddClientModal( type: widget.type, onConfirm: confirmAddItem, diff --git a/lib/screens/settings/advanced_setings.dart b/lib/screens/settings/advanced_setings.dart index 66ff49b..75572c1 100644 --- a/lib/screens/settings/advanced_setings.dart +++ b/lib/screens/settings/advanced_setings.dart @@ -6,6 +6,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; @@ -16,6 +17,8 @@ class AdvancedSettings extends StatelessWidget { Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + Future updateSettings({ required bool newStatus, required Future Function(bool) function @@ -40,6 +43,7 @@ class AdvancedSettings extends StatelessWidget { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.advancedSettings), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, ), body: ListView( children: [ diff --git a/lib/screens/settings/customization/customization.dart b/lib/screens/settings/customization/customization.dart index 98a7121..6ddd5e4 100644 --- a/lib/screens/settings/customization/customization.dart +++ b/lib/screens/settings/customization/customization.dart @@ -1,4 +1,3 @@ -import 'package:adguard_home_manager/functions/generate_color_translation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -9,6 +8,8 @@ import 'package:adguard_home_manager/screens/settings/customization/theme_mode_b import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; +import 'package:adguard_home_manager/functions/generate_color_translation.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/constants/colors.dart'; @@ -56,10 +57,13 @@ class _CustomizationWidgetState extends State { Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.customization), centerTitle: false, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, ), body: ListView( children: [ diff --git a/lib/screens/settings/dhcp/dhcp.dart b/lib/screens/settings/dhcp/dhcp.dart index 724a33a..aa44414 100644 --- a/lib/screens/settings/dhcp/dhcp.dart +++ b/lib/screens/settings/dhcp/dhcp.dart @@ -12,6 +12,7 @@ import 'package:adguard_home_manager/widgets/confirm_action_modal.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_leases.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/select_interface_modal.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/dhcp_provider.dart'; @@ -337,6 +338,7 @@ class _DhcpScreenState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => SelectInterfaceModal( interfaces: dhcpProvider.dhcp!.networkInterfaces, onSelect: (i) => setState(() { @@ -353,498 +355,11 @@ class _DhcpScreenState extends State { }); } - Widget generateBody() { - switch (dhcpProvider.loadStatus) { - case LoadStatus.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)!.loadingDhcp, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - if (selectedInterface != null) { - return SingleChildScrollView( - child: Wrap( - children: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - left: 16, - right: 16 - ), - child: Material( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(28), - child: InkWell( - onTap: selectedInterface != null - ? () => setState(() => enabled = !enabled) - : null, - borderRadius: BorderRadius.circular(28), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 12 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.enableDhcpServer, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - if (selectedInterface != null) ...[ - Text( - selectedInterface!.name, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).listTileTheme.textColor - ), - ) - ] - ], - ), - Switch( - value: enabled, - onChanged: selectedInterface != null - ? (value) => setState(() => enabled = value) - : null, - ), - ], - ), - ), - ), - ), - ), - if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[ - SectionLabel( - label: AppLocalizations.of(context)!.ipv4settings, - padding: const EdgeInsets.only( - top: 24, left: 16, right: 16, bottom: 8 - ) - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv4StartRangeController, - onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.skip_previous_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv4StartRangeError, - labelText: AppLocalizations.of(context)!.startOfRange, - ), - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv4EndRangeController, - onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.skip_next_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv4EndRangeError, - labelText: AppLocalizations.of(context)!.endOfRange, - ), - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv4SubnetMaskController, - onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.hub_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv4SubnetMaskError, - labelText: AppLocalizations.of(context)!.subnetMask, - ), - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv4GatewayController, - onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.router_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv4GatewayError, - labelText: AppLocalizations.of(context)!.gateway, - ), - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: 1, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv4LeaseTimeController, - onChanged: (value) { - if (int.tryParse(value).runtimeType == int) { - setState(() => ipv4LeaseTimeError = null); - } - else { - setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); - } - }, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.timer), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv4LeaseTimeError, - labelText: AppLocalizations.of(context)!.leaseTime, - ), - keyboardType: TextInputType.number, - ), - ), - ), - ], - if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[ - SectionLabel( - label: AppLocalizations.of(context)!.ipv6settings, - padding: const EdgeInsets.all(16) - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 8, bottom: 12, left: 16, right: 8) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv6StartRangeController, - onChanged: (value) => validateIpV4(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.skip_next_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv6StartRangeError, - labelText: AppLocalizations.of(context)!.startOfRange, - ), - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 8, bottom: 12, left: 8, right: 16) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv6EndRangeController, - onChanged: (value) => validateIpV4(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.skip_previous_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv6EndRangeError, - labelText: AppLocalizations.of(context)!.endOfRange, - ), - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: 1, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv6LeaseTimeController, - onChanged: (value) { - if (int.tryParse(value).runtimeType == int) { - setState(() => ipv6LeaseTimeError = null); - } - else { - setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); - } - }, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.timer), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv6LeaseTimeError, - labelText: AppLocalizations.of(context)!.leaseTime, - ), - keyboardType: TextInputType.number, - ), - ), - ), - ], - const SizedBox(height: 20), - SectionLabel( - label: AppLocalizations.of(context)!.dhcpLeases, - padding: const EdgeInsets.all(16), - ), - if (width <= 900) Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - Navigator.push(context, MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.leases, - staticLeases: false, - ) - )); - }, - child: Container( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.dhcpLeases, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - Icon( - Icons.arrow_forward_rounded, - color: Theme.of(context).colorScheme.onSurface, - ) - ], - ), - ), - ), - ), - if (width <= 900) Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - Navigator.push(context, MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, - staticLeases: true, - ) - )); - }, - child: Container( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.dhcpStatic, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - Icon( - Icons.arrow_forward_rounded, - color: Theme.of(context).colorScheme.onSurface, - ) - ], - ), - ), - ), - ), - if (width > 900) Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ElevatedButton( - onPressed: () { - if (!(Platform.isAndroid || Platform.isIOS)) { - SplitView.of(context).push( - DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.leases, - staticLeases: false, - ) - ); - } - else { - Navigator.push(context, MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.leases, - staticLeases: false, - ) - )); - } - }, - child: Row( - children: [ - Text(AppLocalizations.of(context)!.dhcpLeases), - const SizedBox(width: 8), - const Icon(Icons.arrow_forward_rounded) - ], - ) - ), - ElevatedButton( - onPressed: () { - if (!(Platform.isAndroid || Platform.isIOS)) { - SplitView.of(context).push( - DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, - staticLeases: true, - ) - ); - } - else { - Navigator.push(context, MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, - staticLeases: true, - ) - )); - } - }, - child: Row( - children: [ - Text(AppLocalizations.of(context)!.dhcpStatic), - const SizedBox(width: 8), - const Icon(Icons.arrow_forward_rounded) - ], - ) - ), - ], - ), - const SizedBox(height: 10) - ], - ), - ); - } - else { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - AppLocalizations.of(context)!.neededSelectInterface, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5) - ), - ), - ), - const SizedBox(height: 30), - ElevatedButton( - onPressed: selectInterface, - child: Text(AppLocalizations.of(context)!.selectInterface) - ), - ], - ), - ), - ], - ); - } - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.dhcpSettingsNotLoaded, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - } - - return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.dhcpSettings), centerTitle: false, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: selectedInterface != null ? [ IconButton( onPressed: checkDataValid() == true @@ -890,7 +405,494 @@ class _DhcpScreenState extends State { const SizedBox(width: 10) ] : null, ), - body: generateBody(), + body: Builder( + builder: (context) { + switch (dhcpProvider.loadStatus) { + case LoadStatus.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)!.loadingDhcp, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + case LoadStatus.loaded: + if (selectedInterface != null) { + return SingleChildScrollView( + child: Wrap( + children: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + left: 16, + right: 16 + ), + child: Material( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(28), + child: InkWell( + onTap: selectedInterface != null + ? () => setState(() => enabled = !enabled) + : null, + borderRadius: BorderRadius.circular(28), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 12 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.enableDhcpServer, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + if (selectedInterface != null) ...[ + Text( + selectedInterface!.name, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).listTileTheme.textColor + ), + ) + ] + ], + ), + Switch( + value: enabled, + onChanged: selectedInterface != null + ? (value) => setState(() => enabled = value) + : null, + ), + ], + ), + ), + ), + ), + ), + if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[ + SectionLabel( + label: AppLocalizations.of(context)!.ipv4settings, + padding: const EdgeInsets.only( + top: 24, left: 16, right: 16, bottom: 8 + ) + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv4StartRangeController, + onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.skip_previous_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv4StartRangeError, + labelText: AppLocalizations.of(context)!.startOfRange, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv4EndRangeController, + onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.skip_next_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv4EndRangeError, + labelText: AppLocalizations.of(context)!.endOfRange, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv4SubnetMaskController, + onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.hub_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv4SubnetMaskError, + labelText: AppLocalizations.of(context)!.subnetMask, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv4GatewayController, + onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.router_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv4GatewayError, + labelText: AppLocalizations.of(context)!.gateway, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: 1, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv4LeaseTimeController, + onChanged: (value) { + if (int.tryParse(value).runtimeType == int) { + setState(() => ipv4LeaseTimeError = null); + } + else { + setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); + } + }, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.timer), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv4LeaseTimeError, + labelText: AppLocalizations.of(context)!.leaseTime, + ), + keyboardType: TextInputType.number, + ), + ), + ), + ], + if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[ + SectionLabel( + label: AppLocalizations.of(context)!.ipv6settings, + padding: const EdgeInsets.all(16) + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 8, bottom: 12, left: 16, right: 8) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv6StartRangeController, + onChanged: (value) => validateIpV4(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.skip_next_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv6StartRangeError, + labelText: AppLocalizations.of(context)!.startOfRange, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 8, bottom: 12, left: 8, right: 16) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv6EndRangeController, + onChanged: (value) => validateIpV4(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.skip_previous_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv6EndRangeError, + labelText: AppLocalizations.of(context)!.endOfRange, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: 1, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv6LeaseTimeController, + onChanged: (value) { + if (int.tryParse(value).runtimeType == int) { + setState(() => ipv6LeaseTimeError = null); + } + else { + setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); + } + }, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.timer), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv6LeaseTimeError, + labelText: AppLocalizations.of(context)!.leaseTime, + ), + keyboardType: TextInputType.number, + ), + ), + ), + ], + const SizedBox(height: 20), + SectionLabel( + label: AppLocalizations.of(context)!.dhcpLeases, + padding: const EdgeInsets.all(16), + ), + if (width <= 900) Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Navigator.push(context, MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.leases, + staticLeases: false, + ) + )); + }, + child: Container( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.dhcpLeases, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + Icon( + Icons.arrow_forward_rounded, + color: Theme.of(context).colorScheme.onSurface, + ) + ], + ), + ), + ), + ), + if (width <= 900) Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Navigator.push(context, MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, + staticLeases: true, + ) + )); + }, + child: Container( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.dhcpStatic, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + Icon( + Icons.arrow_forward_rounded, + color: Theme.of(context).colorScheme.onSurface, + ) + ], + ), + ), + ), + ), + if (width > 900) Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: () { + if (!(Platform.isAndroid || Platform.isIOS)) { + SplitView.of(context).push( + DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.leases, + staticLeases: false, + ) + ); + } + else { + Navigator.push(context, MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.leases, + staticLeases: false, + ) + )); + } + }, + child: Row( + children: [ + Text(AppLocalizations.of(context)!.dhcpLeases), + const SizedBox(width: 8), + const Icon(Icons.arrow_forward_rounded) + ], + ) + ), + ElevatedButton( + onPressed: () { + if (!(Platform.isAndroid || Platform.isIOS)) { + SplitView.of(context).push( + DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, + staticLeases: true, + ) + ); + } + else { + Navigator.push(context, MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, + staticLeases: true, + ) + )); + } + }, + child: Row( + children: [ + Text(AppLocalizations.of(context)!.dhcpStatic), + const SizedBox(width: 8), + const Icon(Icons.arrow_forward_rounded) + ], + ) + ), + ], + ), + const SizedBox(height: 10) + ], + ), + ); + } + else { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + AppLocalizations.of(context)!.neededSelectInterface, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5) + ), + ), + ), + const SizedBox(height: 30), + ElevatedButton( + onPressed: selectInterface, + child: Text(AppLocalizations.of(context)!.selectInterface) + ), + ], + ), + ), + ], + ); + } + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.dhcpSettingsNotLoaded, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, + ) ); } } \ No newline at end of file diff --git a/lib/screens/settings/dhcp/dhcp_leases.dart b/lib/screens/settings/dhcp/dhcp_leases.dart index 3763704..60a5644 100644 --- a/lib/screens/settings/dhcp/dhcp_leases.dart +++ b/lib/screens/settings/dhcp/dhcp_leases.dart @@ -2,6 +2,7 @@ import 'dart:io'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:animations/animations.dart'; @@ -108,6 +109,7 @@ class DhcpLeases extends StatelessWidget { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => AddStaticLeaseModal( onConfirm: createLease, dialog: false, @@ -120,6 +122,7 @@ class DhcpLeases extends StatelessWidget { return Scaffold( appBar: AppBar( + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, title: Text( staticLeases == true ? AppLocalizations.of(context)!.dhcpStatic diff --git a/lib/screens/settings/dns/bootstrap_dns.dart b/lib/screens/settings/dns/bootstrap_dns.dart index 055042d..291601a 100644 --- a/lib/screens/settings/dns/bootstrap_dns.dart +++ b/lib/screens/settings/dns/bootstrap_dns.dart @@ -1,5 +1,6 @@ // ignore_for_file: use_build_context_synchronously +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -66,6 +67,8 @@ class _BootstrapDnsScreenState extends State { final dnsProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + void saveData() async { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.savingConfig); @@ -102,6 +105,7 @@ class _BootstrapDnsScreenState extends State { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.bootstrapDns), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ IconButton( onPressed: validValues == true diff --git a/lib/screens/settings/dns/cache_config.dart b/lib/screens/settings/dns/cache_config.dart index eb5aad3..b092a8b 100644 --- a/lib/screens/settings/dns/cache_config.dart +++ b/lib/screens/settings/dns/cache_config.dart @@ -7,6 +7,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; import 'package:adguard_home_manager/screens/settings/dns/clear_dns_cache_dialog.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/dns_provider.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -69,6 +70,8 @@ class _CacheConfigDnsScreenState extends State { final dnsProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + void saveData() async { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.savingConfig); @@ -154,6 +157,7 @@ class _CacheConfigDnsScreenState extends State { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.dnsCacheConfig), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ IconButton( onPressed: validData == true diff --git a/lib/screens/settings/dns/dns.dart b/lib/screens/settings/dns/dns.dart index eac3c2c..1aa63fd 100644 --- a/lib/screens/settings/dns/dns.dart +++ b/lib/screens/settings/dns/dns.dart @@ -1,9 +1,6 @@ // ignore_for_file: use_build_context_synchronously import 'dart:io'; - -import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/providers/dns_provider.dart'; import 'package:flutter/material.dart'; import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:provider/provider.dart'; @@ -17,13 +14,21 @@ import 'package:adguard_home_manager/screens/settings/dns/private_reverse_server import 'package:adguard_home_manager/screens/settings/dns/upstream_dns.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; +import 'package:adguard_home_manager/providers/dns_provider.dart'; import 'package:adguard_home_manager/functions/clear_dns_cache.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class DnsSettings extends StatefulWidget { - const DnsSettings({Key? key}) : super(key: key); + final bool splitView; + + const DnsSettings({ + Key? key, + required this.splitView, + }) : super(key: key); @override State createState() => _DnsSettingsState(); @@ -44,106 +49,17 @@ class _DnsSettingsState extends State { final width = MediaQuery.of(context).size.width; - void navigate(Widget widget) { - if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { - SplitView.of(context).push(widget); + void navigate(Widget w) { + if (widget.splitView) { + SplitView.of(context).push(w); } else { Navigator.push(context, MaterialPageRoute( - builder: (context) => widget + builder: (context) => w )); } } - Widget generateBody() { - switch (dnsProvider.loadStatus) { - case LoadStatus.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)!.loadingDnsConfig, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ) - ); - - case LoadStatus.loaded: - return ListView( - children: [ - CustomListTile( - title: AppLocalizations.of(context)!.upstreamDns, - subtitle: AppLocalizations.of(context)!.upstreamDnsDescription, - onTap: () => navigate(const UpstreamDnsScreen()), - icon: Icons.upload_rounded, - ), - CustomListTile( - title: AppLocalizations.of(context)!.bootstrapDns, - subtitle: AppLocalizations.of(context)!.bootstrapDnsDescription, - onTap: () => navigate(const BootstrapDnsScreen()), - icon: Icons.dns_rounded, - ), - CustomListTile( - title: AppLocalizations.of(context)!.privateReverseDnsServers, - subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription, - onTap: () => navigate(const PrivateReverseDnsServersScreen()), - icon: Icons.person_rounded, - ), - CustomListTile( - title: AppLocalizations.of(context)!.dnsServerSettings, - subtitle: AppLocalizations.of(context)!.dnsServerSettingsDescription, - onTap: () => navigate(const DnsServerSettingsScreen()), - icon: Icons.settings, - ), - CustomListTile( - title: AppLocalizations.of(context)!.dnsCacheConfig, - subtitle: AppLocalizations.of(context)!.dnsCacheConfigDescription, - onTap: () => navigate(const CacheConfigDnsScreen()), - icon: Icons.storage_rounded, - ), - ], - ); - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.dnsConfigNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - } - void clearCache() async { final result = await clearDnsCache(context, serversProvider.selectedServer!); if (result == true) { @@ -165,6 +81,7 @@ class _DnsSettingsState extends State { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.dnsSettings), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ PopupMenuButton( itemBuilder: (context) => [ @@ -200,7 +117,96 @@ class _DnsSettingsState extends State { const SizedBox(width: 10) ], ), - body: generateBody(), + body: Builder( + builder: (context) { + switch (dnsProvider.loadStatus) { + case LoadStatus.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)!.loadingDnsConfig, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ) + ); + + case LoadStatus.loaded: + return ListView( + children: [ + CustomListTile( + title: AppLocalizations.of(context)!.upstreamDns, + subtitle: AppLocalizations.of(context)!.upstreamDnsDescription, + onTap: () => navigate(const UpstreamDnsScreen()), + icon: Icons.upload_rounded, + ), + CustomListTile( + title: AppLocalizations.of(context)!.bootstrapDns, + subtitle: AppLocalizations.of(context)!.bootstrapDnsDescription, + onTap: () => navigate(const BootstrapDnsScreen()), + icon: Icons.dns_rounded, + ), + CustomListTile( + title: AppLocalizations.of(context)!.privateReverseDnsServers, + subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription, + onTap: () => navigate(const PrivateReverseDnsServersScreen()), + icon: Icons.person_rounded, + ), + CustomListTile( + title: AppLocalizations.of(context)!.dnsServerSettings, + subtitle: AppLocalizations.of(context)!.dnsServerSettingsDescription, + onTap: () => navigate(const DnsServerSettingsScreen()), + icon: Icons.settings, + ), + CustomListTile( + title: AppLocalizations.of(context)!.dnsCacheConfig, + subtitle: AppLocalizations.of(context)!.dnsCacheConfigDescription, + onTap: () => navigate(const CacheConfigDnsScreen()), + icon: Icons.storage_rounded, + ), + ], + ); + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.dnsConfigNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, + ) ); } } \ No newline at end of file diff --git a/lib/screens/settings/dns/dns_server_settings.dart b/lib/screens/settings/dns/dns_server_settings.dart index 9169408..b8481a0 100644 --- a/lib/screens/settings/dns/dns_server_settings.dart +++ b/lib/screens/settings/dns/dns_server_settings.dart @@ -8,6 +8,7 @@ import 'package:adguard_home_manager/widgets/custom_radio_list_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/providers/dns_provider.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; @@ -99,6 +100,7 @@ class _DnsServerSettingsScreenState extends State { Widget build(BuildContext context) { final dnsProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; void saveData() async { ProcessModal processModal = ProcessModal(context: context); @@ -153,6 +155,7 @@ class _DnsServerSettingsScreenState extends State { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.dnsServerSettings), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ IconButton( onPressed: isDataValid == true diff --git a/lib/screens/settings/dns/private_reverse_servers.dart b/lib/screens/settings/dns/private_reverse_servers.dart index eb0f16a..2373d44 100644 --- a/lib/screens/settings/dns/private_reverse_servers.dart +++ b/lib/screens/settings/dns/private_reverse_servers.dart @@ -6,6 +6,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/providers/dns_provider.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; @@ -90,6 +91,8 @@ class _PrivateReverseDnsServersScreenState extends State(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + void saveData() async { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.savingConfig); @@ -134,6 +137,7 @@ class _PrivateReverseDnsServersScreenState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => CommentModal( onConfirm: (value) { setState(() { @@ -123,6 +125,7 @@ class _UpstreamDnsScreenState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => CommentModal( comment: item['comment'], onConfirm: (value) { @@ -174,6 +177,7 @@ class _UpstreamDnsScreenState extends State { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.upstreamDns), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ IconButton( onPressed: validValues == true diff --git a/lib/screens/settings/dns_rewrites/dns_rewrites.dart b/lib/screens/settings/dns_rewrites/dns_rewrites.dart index fda731e..cd4c8bd 100644 --- a/lib/screens/settings/dns_rewrites/dns_rewrites.dart +++ b/lib/screens/settings/dns_rewrites/dns_rewrites.dart @@ -11,6 +11,7 @@ import 'package:adguard_home_manager/screens/settings/dns_rewrites/delete_dns_re import 'package:adguard_home_manager/screens/settings/dns_rewrites/dns_rewrite_modal.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/rewrite_rules_provider.dart'; @@ -129,193 +130,195 @@ class _DnsRewritesScreenState extends State { } } - Widget generateBody() { - switch (rewriteRulesProvider.loadStatus) { - case LoadStatus.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)!.loadingRewriteRules, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - if (rewriteRulesProvider.rewriteRules!.isNotEmpty) { - return RefreshIndicator( - onRefresh: () async { - final result = await rewriteRulesProvider.fetchRules(); - if (result == false) { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.rewriteRulesNotLoaded, - color: Colors.red - ); - } - }, - child: ListView.builder( - controller: scrollController, - padding: const EdgeInsets.only(top: 0), - itemCount: rewriteRulesProvider.rewriteRules!.length, - itemBuilder: (context, index) => Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: InkWell( - onTap: () => { - if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => DnsRewriteModal( - onConfirm: updateRewriteRule, - dialog: true, - rule: rewriteRulesProvider.rewriteRules![index], - onDelete: (rule) => showDialog( - context: context, - builder: (context) => DeleteDnsRewrite( - onConfirm: () => deleteDnsRewrite(rule) - ) - ), - ), - ) - } - else { - showModalBottomSheet( - context: context, - builder: (context) => DnsRewriteModal( - onConfirm: updateRewriteRule, - dialog: false, - rule: rewriteRulesProvider.rewriteRules![index], - onDelete: (rule) => showDialog( - context: context, - builder: (context) => DeleteDnsRewrite( - onConfirm: () => deleteDnsRewrite(rule) - ) - ), - ), - backgroundColor: Colors.transparent, - isScrollControlled: true, - ) - } - }, - borderRadius: BorderRadius.circular(10), - child: Padding( - padding: const EdgeInsets.only( - left: 16, top: 16, bottom: 16, right: 8 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - "${AppLocalizations.of(context)!.domain}: ", - style: TextStyle( - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Text( - rewriteRulesProvider.rewriteRules![index].domain, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ), - const SizedBox(height: 3), - Row( - children: [ - Text( - "${AppLocalizations.of(context)!.answer}: ", - style: TextStyle( - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Text( - rewriteRulesProvider.rewriteRules![index].answer, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ), - ], - ), - Icon( - Icons.keyboard_arrow_right_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ) - ], - ), - ), - ), - ) - ), - ); - } - else { - return Center( - child: Text( - AppLocalizations.of(context)!.noRewriteRules, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ); - } - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.rewriteRulesNotLoaded, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - } - return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.dnsRewrites), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, centerTitle: false, ), body: Stack( children: [ - generateBody(), + Builder( + builder: (context) { + switch (rewriteRulesProvider.loadStatus) { + case LoadStatus.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)!.loadingRewriteRules, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + case LoadStatus.loaded: + if (rewriteRulesProvider.rewriteRules!.isNotEmpty) { + return RefreshIndicator( + onRefresh: () async { + final result = await rewriteRulesProvider.fetchRules(); + if (result == false) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.rewriteRulesNotLoaded, + color: Colors.red + ); + } + }, + child: ListView.builder( + controller: scrollController, + padding: const EdgeInsets.only(top: 0), + itemCount: rewriteRulesProvider.rewriteRules!.length, + itemBuilder: (context, index) => Card( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: InkWell( + onTap: () => { + if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (context) => DnsRewriteModal( + onConfirm: updateRewriteRule, + dialog: true, + rule: rewriteRulesProvider.rewriteRules![index], + onDelete: (rule) => showDialog( + context: context, + builder: (context) => DeleteDnsRewrite( + onConfirm: () => deleteDnsRewrite(rule) + ) + ), + ), + ) + } + else { + showModalBottomSheet( + context: context, + useRootNavigator: true, + builder: (context) => DnsRewriteModal( + onConfirm: updateRewriteRule, + dialog: false, + rule: rewriteRulesProvider.rewriteRules![index], + onDelete: (rule) => showDialog( + context: context, + builder: (context) => DeleteDnsRewrite( + onConfirm: () => deleteDnsRewrite(rule) + ) + ), + ), + backgroundColor: Colors.transparent, + isScrollControlled: true, + ) + } + }, + borderRadius: BorderRadius.circular(10), + child: Padding( + padding: const EdgeInsets.only( + left: 16, top: 16, bottom: 16, right: 8 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + "${AppLocalizations.of(context)!.domain}: ", + style: TextStyle( + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + Text( + rewriteRulesProvider.rewriteRules![index].domain, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + const SizedBox(height: 3), + Row( + children: [ + Text( + "${AppLocalizations.of(context)!.answer}: ", + style: TextStyle( + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + Text( + rewriteRulesProvider.rewriteRules![index].answer, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ], + ), + Icon( + Icons.keyboard_arrow_right_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ) + ], + ), + ), + ), + ) + ), + ); + } + else { + return Center( + child: Text( + AppLocalizations.of(context)!.noRewriteRules, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ); + } + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.rewriteRulesNotLoaded, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, + ), AnimatedPositioned( duration: const Duration(milliseconds: 100), curve: Curves.easeInOut, @@ -344,6 +347,7 @@ class _DnsRewritesScreenState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => DnsRewriteModal( onConfirm: addDnsRewrite, dialog: false, diff --git a/lib/screens/settings/encryption/encryption.dart b/lib/screens/settings/encryption/encryption.dart index de75918..3c4744c 100644 --- a/lib/screens/settings/encryption/encryption.dart +++ b/lib/screens/settings/encryption/encryption.dart @@ -14,6 +14,7 @@ import 'package:adguard_home_manager/screens/settings/encryption/encryption_func import 'package:adguard_home_manager/screens/settings/encryption/error_message.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/base64.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; @@ -272,379 +273,10 @@ class _EncryptionSettingsWidgetState extends State { } } - Widget generateBody() { - 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)!.loadingEncryptionSettings, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ) - ); - - case 1: - return ListView( - children: [ - EncryptionMasterSwitch( - value: enabled, - onChange: (value) { - setState(() => enabled = value); - onEditValidate(); - } - ), - SectionLabel( - label: AppLocalizations.of(context)!.serverConfiguration, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - ), - EncryptionTextField( - enabled: enabled, - controller: domainNameController, - icon: Icons.link_rounded, - onChanged: (value) { - setState(() => domainError = validateDomain(context, value)); - onEditValidate(); - }, - errorText: domainError, - label: AppLocalizations.of(context)!.domainName, - helperText: AppLocalizations.of(context)!.domainNameDescription, - ), - const SizedBox(height: 10), - CustomSwitchListTile( - value: redirectHttps, - onChanged: (value) { - setState(() => redirectHttps = value); - onEditValidate(); - }, - title: AppLocalizations.of(context)!.redirectHttps, - disabled: !enabled, - ), - const SizedBox(height: 10), - Wrap( - children: [ - FractionallySizedBox( - widthFactor: width > 900 ? 0.33 : 1, - child: EncryptionTextField( - enabled: enabled, - controller: httpsPortController, - icon: Icons.numbers_rounded, - onChanged: (value) { - setState(() => httpsPortError = validatePort(context, value)); - onEditValidate(); - }, - errorText: httpsPortError, - label: AppLocalizations.of(context)!.httpsPort, - keyboardType: TextInputType.number, - ), - ), - Padding( - padding: width <= 900 - ? const EdgeInsets.symmetric(vertical: 24) - : const EdgeInsets.all(0), - child: FractionallySizedBox( - widthFactor: width > 900 ? 0.33 : 1, - child: EncryptionTextField( - enabled: enabled, - controller: tlsPortController, - icon: Icons.numbers_rounded, - onChanged: (value) { - setState(() => tlsPortError = validatePort(context, value)); - onEditValidate(); - }, - errorText: tlsPortError, - label: AppLocalizations.of(context)!.tlsPort, - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.33 : 1, - child: EncryptionTextField( - enabled: enabled, - controller: dnsOverQuicPortController, - icon: Icons.numbers_rounded, - onChanged: (value) { - setState(() => dnsOverQuicPortError = validatePort(context, value)); - onEditValidate(); - }, - errorText: dnsOverQuicPortError, - label: AppLocalizations.of(context)!.dnsOverQuicPort, - keyboardType: TextInputType.number, - ), - ), - ], - ), - SectionLabel( - label: AppLocalizations.of(context)!.certificates, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - ), - Card( - margin: const EdgeInsets.symmetric(horizontal: 16), - child: Padding( - padding: const EdgeInsets.all(20), - child: Row( - children: [ - Icon( - Icons.info_rounded, - color: Theme.of(context).listTileTheme.iconColor, - ), - const SizedBox(width: 20), - Flexible( - child: Text( - AppLocalizations.of(context)!.certificatesDescription, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - ), - ) - ) - ], - ), - ), - ), - const SizedBox(height: 20), - RadioListTile( - value: 0, - groupValue: certificateOption, - onChanged: enabled == true - ? (value) { - setState(() => certificateOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.certificateFilePath, - style: const TextStyle( - fontWeight: FontWeight.normal - ), - ), - ), - RadioListTile( - value: 1, - groupValue: certificateOption, - onChanged: enabled == true - ? (value) { - setState(() => certificateOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.pasteCertificateContent, - style: const TextStyle( - fontWeight: FontWeight.normal - ), - ), - ), - const SizedBox(height: 10), - if (certificateOption == 0) EncryptionTextField( - enabled: enabled, - controller: certificatePathController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => certificatePathError = validatePath(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.certificatePath, - errorText: certificatePathError, - ), - if (certificateOption == 1) EncryptionTextField( - enabled: enabled, - controller: certificateContentController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => certificateContentError = validateCertificate(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.certificateContent, - errorText: certificateContentError, - multiline: true, - keyboardType: TextInputType.multiline, - ), - if (certKeyValid != null && (certificateContentController.text != '' || certificatePathController.text != '')) ...[ - const SizedBox(height: 20), - if (certKeyValid!['valid_chain'] != null) ...[ - Status( - valid: certKeyValid!['valid_chain'], - label: certKeyValid!['valid_chain'] == true - ? AppLocalizations.of(context)!.validCertificateChain - : AppLocalizations.of(context)!.invalidCertificateChain, - ), - const SizedBox(height: 10), - ], - if (certKeyValid!['subject'] != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.subject}: ${certKeyValid!['subject']}" - ), - const SizedBox(height: 10), - ], - if (certKeyValid!['issuer'] != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.issuer}: ${certKeyValid!['issuer']}" - ), - const SizedBox(height: 10), - ], - if (certKeyValid!['not_after'] != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.expirationDate}: ${certKeyValid!['not_after']}" - ), - const SizedBox(height: 10), - ], - if (certKeyValid!['dns_names'] != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.hostNames}: ${certKeyValid!['dns_names'].join(', ')}" - ), - const SizedBox(height: 10), - ], - ], - SectionLabel( - label: AppLocalizations.of(context)!.privateKey, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - ), - RadioListTile( - value: 0, - groupValue: privateKeyOption, - onChanged: enabled == true - ? (value) { - setState(() => privateKeyOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.privateKeyFile, - style: const TextStyle( - fontWeight: FontWeight.normal - ), - ), - ), - RadioListTile( - value: 1, - groupValue: privateKeyOption, - onChanged: enabled == true - ? (value) { - setState(() => privateKeyOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.pastePrivateKey, - style: const TextStyle( - fontWeight: FontWeight.normal - ), - ), - ), - if (privateKeyOption == 0) const SizedBox(height: 10), - if (privateKeyOption == 1) ...[ - CustomSwitchListTile( - value: usePreviouslySavedKey, - onChanged: (value) => setState(() => usePreviouslySavedKey = value), - title: AppLocalizations.of(context)!.usePreviousKey, - ), - const SizedBox(height: 10) - ], - if (privateKeyOption == 0) EncryptionTextField( - enabled: enabled, - controller: privateKeyPathController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => privateKeyPathError = validatePath(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.privateKeyPath, - errorText: privateKeyPathError, - ), - if (privateKeyOption == 1) EncryptionTextField( - enabled: enabled == true - ? !usePreviouslySavedKey - : false, - controller: pastePrivateKeyController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => pastePrivateKeyError = validatePrivateKey(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.pastePrivateKey, - errorText: pastePrivateKeyError, - keyboardType: TextInputType.multiline, - multiline: true, - ), - const SizedBox(height: 20), - if (certKeyValid != null && (privateKeyPathController.text != '' || pastePrivateKeyController.text != '' || usePreviouslySavedKey == true)) ...[ - if (certKeyValid!['valid_key'] != null) ...[ - Status( - valid: certKeyValid!['valid_key'], - label: certKeyValid!['valid_key'] == true - ? AppLocalizations.of(context)!.validPrivateKey - : AppLocalizations.of(context)!.invalidPrivateKey, - ), - const SizedBox(height: 10) - ], - if (certKeyValid!['valid_pair'] != null && certKeyValid!['valid_pair'] == false) ...[ - Status( - valid: false, - label: AppLocalizations.of(context)!.keysNotMatch, - ), - const SizedBox(height: 10) - ], - if (certKeyValid!['key_type'] != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.keyType}: ${certKeyValid!['key_type']}" - ), - const SizedBox(height: 10), - ], - const SizedBox(height: 10) - ] - ], - ); - - 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)!.encryptionSettingsNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - } - return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.encryptionSettings), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, centerTitle: false, actions: [ IconButton( @@ -668,7 +300,377 @@ class _EncryptionSettingsWidgetState extends State { const SizedBox(width: 10), ], ), - body: generateBody(), + body: 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)!.loadingEncryptionSettings, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ) + ); + + case 1: + return ListView( + children: [ + EncryptionMasterSwitch( + value: enabled, + onChange: (value) { + setState(() => enabled = value); + onEditValidate(); + } + ), + SectionLabel( + label: AppLocalizations.of(context)!.serverConfiguration, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + ), + EncryptionTextField( + enabled: enabled, + controller: domainNameController, + icon: Icons.link_rounded, + onChanged: (value) { + setState(() => domainError = validateDomain(context, value)); + onEditValidate(); + }, + errorText: domainError, + label: AppLocalizations.of(context)!.domainName, + helperText: AppLocalizations.of(context)!.domainNameDescription, + ), + const SizedBox(height: 10), + CustomSwitchListTile( + value: redirectHttps, + onChanged: (value) { + setState(() => redirectHttps = value); + onEditValidate(); + }, + title: AppLocalizations.of(context)!.redirectHttps, + disabled: !enabled, + ), + const SizedBox(height: 10), + Wrap( + children: [ + FractionallySizedBox( + widthFactor: width > 900 ? 0.33 : 1, + child: EncryptionTextField( + enabled: enabled, + controller: httpsPortController, + icon: Icons.numbers_rounded, + onChanged: (value) { + setState(() => httpsPortError = validatePort(context, value)); + onEditValidate(); + }, + errorText: httpsPortError, + label: AppLocalizations.of(context)!.httpsPort, + keyboardType: TextInputType.number, + ), + ), + Padding( + padding: width <= 900 + ? const EdgeInsets.symmetric(vertical: 24) + : const EdgeInsets.all(0), + child: FractionallySizedBox( + widthFactor: width > 900 ? 0.33 : 1, + child: EncryptionTextField( + enabled: enabled, + controller: tlsPortController, + icon: Icons.numbers_rounded, + onChanged: (value) { + setState(() => tlsPortError = validatePort(context, value)); + onEditValidate(); + }, + errorText: tlsPortError, + label: AppLocalizations.of(context)!.tlsPort, + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.33 : 1, + child: EncryptionTextField( + enabled: enabled, + controller: dnsOverQuicPortController, + icon: Icons.numbers_rounded, + onChanged: (value) { + setState(() => dnsOverQuicPortError = validatePort(context, value)); + onEditValidate(); + }, + errorText: dnsOverQuicPortError, + label: AppLocalizations.of(context)!.dnsOverQuicPort, + keyboardType: TextInputType.number, + ), + ), + ], + ), + SectionLabel( + label: AppLocalizations.of(context)!.certificates, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + ), + Card( + margin: const EdgeInsets.symmetric(horizontal: 16), + child: Padding( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + Icon( + Icons.info_rounded, + color: Theme.of(context).listTileTheme.iconColor, + ), + const SizedBox(width: 20), + Flexible( + child: Text( + AppLocalizations.of(context)!.certificatesDescription, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + ), + ) + ) + ], + ), + ), + ), + const SizedBox(height: 20), + RadioListTile( + value: 0, + groupValue: certificateOption, + onChanged: enabled == true + ? (value) { + setState(() => certificateOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.certificateFilePath, + style: const TextStyle( + fontWeight: FontWeight.normal + ), + ), + ), + RadioListTile( + value: 1, + groupValue: certificateOption, + onChanged: enabled == true + ? (value) { + setState(() => certificateOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.pasteCertificateContent, + style: const TextStyle( + fontWeight: FontWeight.normal + ), + ), + ), + const SizedBox(height: 10), + if (certificateOption == 0) EncryptionTextField( + enabled: enabled, + controller: certificatePathController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => certificatePathError = validatePath(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.certificatePath, + errorText: certificatePathError, + ), + if (certificateOption == 1) EncryptionTextField( + enabled: enabled, + controller: certificateContentController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => certificateContentError = validateCertificate(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.certificateContent, + errorText: certificateContentError, + multiline: true, + keyboardType: TextInputType.multiline, + ), + if (certKeyValid != null && (certificateContentController.text != '' || certificatePathController.text != '')) ...[ + const SizedBox(height: 20), + if (certKeyValid!['valid_chain'] != null) ...[ + Status( + valid: certKeyValid!['valid_chain'], + label: certKeyValid!['valid_chain'] == true + ? AppLocalizations.of(context)!.validCertificateChain + : AppLocalizations.of(context)!.invalidCertificateChain, + ), + const SizedBox(height: 10), + ], + if (certKeyValid!['subject'] != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.subject}: ${certKeyValid!['subject']}" + ), + const SizedBox(height: 10), + ], + if (certKeyValid!['issuer'] != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.issuer}: ${certKeyValid!['issuer']}" + ), + const SizedBox(height: 10), + ], + if (certKeyValid!['not_after'] != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.expirationDate}: ${certKeyValid!['not_after']}" + ), + const SizedBox(height: 10), + ], + if (certKeyValid!['dns_names'] != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.hostNames}: ${certKeyValid!['dns_names'].join(', ')}" + ), + const SizedBox(height: 10), + ], + ], + SectionLabel( + label: AppLocalizations.of(context)!.privateKey, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + ), + RadioListTile( + value: 0, + groupValue: privateKeyOption, + onChanged: enabled == true + ? (value) { + setState(() => privateKeyOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.privateKeyFile, + style: const TextStyle( + fontWeight: FontWeight.normal + ), + ), + ), + RadioListTile( + value: 1, + groupValue: privateKeyOption, + onChanged: enabled == true + ? (value) { + setState(() => privateKeyOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.pastePrivateKey, + style: const TextStyle( + fontWeight: FontWeight.normal + ), + ), + ), + if (privateKeyOption == 0) const SizedBox(height: 10), + if (privateKeyOption == 1) ...[ + CustomSwitchListTile( + value: usePreviouslySavedKey, + onChanged: (value) => setState(() => usePreviouslySavedKey = value), + title: AppLocalizations.of(context)!.usePreviousKey, + ), + const SizedBox(height: 10) + ], + if (privateKeyOption == 0) EncryptionTextField( + enabled: enabled, + controller: privateKeyPathController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => privateKeyPathError = validatePath(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.privateKeyPath, + errorText: privateKeyPathError, + ), + if (privateKeyOption == 1) EncryptionTextField( + enabled: enabled == true + ? !usePreviouslySavedKey + : false, + controller: pastePrivateKeyController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => pastePrivateKeyError = validatePrivateKey(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.pastePrivateKey, + errorText: pastePrivateKeyError, + keyboardType: TextInputType.multiline, + multiline: true, + ), + const SizedBox(height: 20), + if (certKeyValid != null && (privateKeyPathController.text != '' || pastePrivateKeyController.text != '' || usePreviouslySavedKey == true)) ...[ + if (certKeyValid!['valid_key'] != null) ...[ + Status( + valid: certKeyValid!['valid_key'], + label: certKeyValid!['valid_key'] == true + ? AppLocalizations.of(context)!.validPrivateKey + : AppLocalizations.of(context)!.invalidPrivateKey, + ), + const SizedBox(height: 10) + ], + if (certKeyValid!['valid_pair'] != null && certKeyValid!['valid_pair'] == false) ...[ + Status( + valid: false, + label: AppLocalizations.of(context)!.keysNotMatch, + ), + const SizedBox(height: 10) + ], + if (certKeyValid!['key_type'] != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.keyType}: ${certKeyValid!['key_type']}" + ), + const SizedBox(height: 10), + ], + const SizedBox(height: 10) + ] + ], + ); + + 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)!.encryptionSettingsNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, + ) ); } } \ No newline at end of file diff --git a/lib/screens/settings/general_settings/general_settings.dart b/lib/screens/settings/general_settings/general_settings.dart index 9ead014..cef6a8f 100644 --- a/lib/screens/settings/general_settings/general_settings.dart +++ b/lib/screens/settings/general_settings/general_settings.dart @@ -13,6 +13,7 @@ import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/functions/check_app_updates.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/functions/open_url.dart'; import 'package:adguard_home_manager/functions/app_update_download_link.dart'; @@ -34,6 +35,8 @@ class _GeneralSettingsState extends State { Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + Future updateSettings({ required bool newStatus, required Future Function(bool) function @@ -113,6 +116,7 @@ class _GeneralSettingsState extends State { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.generalSettings), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, ), body: ListView( children: [ diff --git a/lib/screens/settings/general_settings/reorderable_top_items_home.dart b/lib/screens/settings/general_settings/reorderable_top_items_home.dart index 75ad9cc..b649ff5 100644 --- a/lib/screens/settings/general_settings/reorderable_top_items_home.dart +++ b/lib/screens/settings/general_settings/reorderable_top_items_home.dart @@ -1,5 +1,6 @@ // ignore_for_file: use_build_context_synchronously +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -91,6 +92,8 @@ class _ReorderableTopItemsHomeState extends State { Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + Widget tile(HomeTopItems title) { switch (title) { case HomeTopItems.queriedDomains: @@ -177,6 +180,7 @@ class _ReorderableTopItemsHomeState extends State { child: Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.topItemsOrder), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ IconButton( onPressed: !listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList) diff --git a/lib/screens/settings/safe_search_settings.dart b/lib/screens/settings/safe_search_settings.dart index 461b6a6..9ef8359 100644 --- a/lib/screens/settings/safe_search_settings.dart +++ b/lib/screens/settings/safe_search_settings.dart @@ -1,5 +1,6 @@ // ignore_for_file: use_build_context_synchronously +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -70,6 +71,8 @@ class _SafeSearchSettingsScreenState extends State { final statusProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + void saveConfig() async { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.savingSettings); @@ -104,167 +107,10 @@ class _SafeSearchSettingsScreenState extends State { } } - Widget body() { - switch (statusProvider.loadStatus) { - case LoadStatus.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)!.loadingSafeSearchSettings, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - return RefreshIndicator( - onRefresh: requestSafeSearchSettings, - child: ListView( - children: [ - Padding( - padding: const EdgeInsets.only( - top: 16, - left: 16, - right: 16, - bottom: 8 - ), - child: Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(28), - child: InkWell( - borderRadius: BorderRadius.circular(28), - onTap: () => setState(() => generalEnabled = !generalEnabled), - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 12 - ), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(28) - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.enableSafeSearch, - style: const TextStyle( - fontSize: 18 - ), - ), - Switch( - value: generalEnabled, - onChanged: (value) => setState(() => generalEnabled = value) - ) - ], - ), - ), - ), - ), - ), - CustomCheckboxListTile( - value: bingEnabled, - onChanged: (value) => setState(() => bingEnabled = value), - title: "Bing", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, - ), - CustomCheckboxListTile( - value: duckduckgoEnabled, - onChanged: (value) => setState(() => duckduckgoEnabled = value), - title: "DuckDuckGo", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, - ), - CustomCheckboxListTile( - value: googleEnabled, - onChanged: (value) => setState(() => googleEnabled = value), - title: "Google", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, - ), - CustomCheckboxListTile( - value: pixabayEnabled, - onChanged: (value) => setState(() => pixabayEnabled = value), - title: "Pixabay", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, - ), - CustomCheckboxListTile( - value: yandexEnabled, - onChanged: (value) => setState(() => yandexEnabled = value), - title: "Yandex", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, - ), - CustomCheckboxListTile( - value: youtubeEnabled, - onChanged: (value) => setState(() => youtubeEnabled = value), - title: "YouTube", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, - ), - ], - ), - ); - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.safeSearchSettingsNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - - default: - return const SizedBox(); - } - } - return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.safeSearchSettings), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, centerTitle: false, actions: [ IconButton( @@ -277,7 +123,165 @@ class _SafeSearchSettingsScreenState extends State { const SizedBox(width: 8) ], ), - body: body(), + body: Builder( + builder: (context) { + switch (statusProvider.loadStatus) { + case LoadStatus.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)!.loadingSafeSearchSettings, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + case LoadStatus.loaded: + return RefreshIndicator( + onRefresh: requestSafeSearchSettings, + child: ListView( + children: [ + Padding( + padding: const EdgeInsets.only( + top: 16, + left: 16, + right: 16, + bottom: 8 + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(28), + child: InkWell( + borderRadius: BorderRadius.circular(28), + onTap: () => setState(() => generalEnabled = !generalEnabled), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 12 + ), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(28) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.enableSafeSearch, + style: const TextStyle( + fontSize: 18 + ), + ), + Switch( + value: generalEnabled, + onChanged: (value) => setState(() => generalEnabled = value) + ) + ], + ), + ), + ), + ), + ), + CustomCheckboxListTile( + value: bingEnabled, + onChanged: (value) => setState(() => bingEnabled = value), + title: "Bing", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: duckduckgoEnabled, + onChanged: (value) => setState(() => duckduckgoEnabled = value), + title: "DuckDuckGo", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: googleEnabled, + onChanged: (value) => setState(() => googleEnabled = value), + title: "Google", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: pixabayEnabled, + onChanged: (value) => setState(() => pixabayEnabled = value), + title: "Pixabay", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: yandexEnabled, + onChanged: (value) => setState(() => yandexEnabled = value), + title: "Yandex", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: youtubeEnabled, + onChanged: (value) => setState(() => youtubeEnabled = value), + title: "YouTube", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, + ), + ], + ), + ); + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.safeSearchSettingsNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + + default: + return const SizedBox(); + } + }, + ) ); } } \ No newline at end of file diff --git a/lib/screens/settings/server_info/server_info.dart b/lib/screens/settings/server_info/server_info.dart index a6a9fcc..2a51a16 100644 --- a/lib/screens/settings/server_info/server_info.dart +++ b/lib/screens/settings/server_info/server_info.dart @@ -1,4 +1,5 @@ import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:animations/animations.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -66,121 +67,123 @@ class _ServerInformationWidgetState extends State { @override Widget build(BuildContext context) { - Widget generateBody() { - switch (serverInfo.loadStatus) { - case LoadStatus.loading: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - AppLocalizations.of(context)!.loadingServerInfo, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - return ListView( - children: [ - CustomListTile( - title: AppLocalizations.of(context)!.dnsAddresses, - subtitle: AppLocalizations.of(context)!.seeDnsAddresses, - onTap: () { - showModal( - context: context, - builder: (context) => DnsAddressesModal( - dnsAddresses: serverInfo.data!.dnsAddresses - ) - ); - }, - ), - CustomListTile( - title: AppLocalizations.of(context)!.dnsPort, - subtitle: serverInfo.data!.dnsPort.toString(), - ), - CustomListTile( - title: AppLocalizations.of(context)!.httpPort, - subtitle: serverInfo.data!.httpPort.toString(), - ), - CustomListTile( - title: AppLocalizations.of(context)!.protectionEnabled, - subtitle: serverInfo.data!.protectionEnabled == true - ? AppLocalizations.of(context)!.yes - : AppLocalizations.of(context)!.no, - ), - CustomListTile( - title: AppLocalizations.of(context)!.dhcpAvailable, - subtitle: serverInfo.data!.dhcpAvailable == true - ? AppLocalizations.of(context)!.yes - : AppLocalizations.of(context)!.no, - ), - CustomListTile( - title: AppLocalizations.of(context)!.serverRunning, - subtitle: serverInfo.data!.running == true - ? AppLocalizations.of(context)!.yes - : AppLocalizations.of(context)!.no, - ), - CustomListTile( - title: AppLocalizations.of(context)!.serverVersion, - subtitle: serverInfo.data!.version, - ), - CustomListTile( - title: AppLocalizations.of(context)!.serverLanguage, - subtitle: serverInfo.data!.language, - ), - ] - ); - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.serverInfoNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - } - + final width = MediaQuery.of(context).size.width; return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.serverInformation), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, centerTitle: false, ), - body: generateBody() + body: Builder( + builder: (context) { + switch (serverInfo.loadStatus) { + case LoadStatus.loading: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + AppLocalizations.of(context)!.loadingServerInfo, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ) + ], + ), + ); + + case LoadStatus.loaded: + return ListView( + children: [ + CustomListTile( + title: AppLocalizations.of(context)!.dnsAddresses, + subtitle: AppLocalizations.of(context)!.seeDnsAddresses, + onTap: () { + showModal( + context: context, + builder: (context) => DnsAddressesModal( + dnsAddresses: serverInfo.data!.dnsAddresses + ) + ); + }, + ), + CustomListTile( + title: AppLocalizations.of(context)!.dnsPort, + subtitle: serverInfo.data!.dnsPort.toString(), + ), + CustomListTile( + title: AppLocalizations.of(context)!.httpPort, + subtitle: serverInfo.data!.httpPort.toString(), + ), + CustomListTile( + title: AppLocalizations.of(context)!.protectionEnabled, + subtitle: serverInfo.data!.protectionEnabled == true + ? AppLocalizations.of(context)!.yes + : AppLocalizations.of(context)!.no, + ), + CustomListTile( + title: AppLocalizations.of(context)!.dhcpAvailable, + subtitle: serverInfo.data!.dhcpAvailable == true + ? AppLocalizations.of(context)!.yes + : AppLocalizations.of(context)!.no, + ), + CustomListTile( + title: AppLocalizations.of(context)!.serverRunning, + subtitle: serverInfo.data!.running == true + ? AppLocalizations.of(context)!.yes + : AppLocalizations.of(context)!.no, + ), + CustomListTile( + title: AppLocalizations.of(context)!.serverVersion, + subtitle: serverInfo.data!.version, + ), + CustomListTile( + title: AppLocalizations.of(context)!.serverLanguage, + subtitle: serverInfo.data!.language, + ), + ] + ); + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.serverInfoNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, + ) ); } } \ No newline at end of file diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index 87d23ef..0f01b44 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -189,7 +189,9 @@ class SettingsWidget extends StatelessWidget { title: AppLocalizations.of(context)!.dnsSettings, subtitle: AppLocalizations.of(context)!.dnsSettingsDescription, thisItem: 3, - screenToNavigate: const DnsSettings(), + screenToNavigate: DnsSettings( + splitView: twoColumns, + ), ), settingsTile( icon: Icons.security_rounded, From a0772032b49bb0ca1174d5b9d7eddc0ba85e7e88 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 02:49:34 +0100 Subject: [PATCH 041/177] Removed unused imports --- lib/screens/filters/add_button.dart | 1 - lib/screens/logs/log_tile.dart | 2 -- lib/screens/settings/dns/dns.dart | 1 - lib/widgets/bottom_nav_bar.dart | 2 -- lib/widgets/layout.dart | 1 - 5 files changed, 7 deletions(-) diff --git a/lib/screens/filters/add_button.dart b/lib/screens/filters/add_button.dart index 7afccf0..e96bb51 100644 --- a/lib/screens/filters/add_button.dart +++ b/lib/screens/filters/add_button.dart @@ -2,7 +2,6 @@ import 'dart:io'; -import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; diff --git a/lib/screens/logs/log_tile.dart b/lib/screens/logs/log_tile.dart index 40f3b0d..14c5262 100644 --- a/lib/screens/logs/log_tile.dart +++ b/lib/screens/logs/log_tile.dart @@ -34,8 +34,6 @@ class LogTile extends StatelessWidget { Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); - final width = MediaQuery.of(context).size.width; - Widget logStatusWidget({ required IconData icon, required Color color, diff --git a/lib/screens/settings/dns/dns.dart b/lib/screens/settings/dns/dns.dart index 1aa63fd..7d0760b 100644 --- a/lib/screens/settings/dns/dns.dart +++ b/lib/screens/settings/dns/dns.dart @@ -1,6 +1,5 @@ // ignore_for_file: use_build_context_synchronously -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:provider/provider.dart'; diff --git a/lib/widgets/bottom_nav_bar.dart b/lib/widgets/bottom_nav_bar.dart index 0f16dcd..490138c 100644 --- a/lib/widgets/bottom_nav_bar.dart +++ b/lib/widgets/bottom_nav_bar.dart @@ -3,7 +3,6 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/config/app_screens.dart'; -import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/models/app_screen.dart'; @@ -15,7 +14,6 @@ class BottomNavBar extends StatelessWidget { Widget build(BuildContext context) { final serversProvider = Provider.of(context); final appConfigProvider = Provider.of(context); - final logsProvider = Provider.of(context); List screens = serversProvider.selectedServer != null && serversProvider.apiClient != null ? screensServerConnected diff --git a/lib/widgets/layout.dart b/lib/widgets/layout.dart index 3513bb9..6104b6f 100644 --- a/lib/widgets/layout.dart +++ b/lib/widgets/layout.dart @@ -1,4 +1,3 @@ -import 'package:adguard_home_manager/widgets/bottom_nav_bar.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:go_router/go_router.dart'; From f966ab7fc59aa54f601511df0be239ab0b4c1bd8 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 02:55:37 +0100 Subject: [PATCH 042/177] Removed base --- lib/base.dart | 116 -------------------------------------------- lib/main.dart | 132 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 88 insertions(+), 160 deletions(-) delete mode 100644 lib/base.dart diff --git a/lib/base.dart b/lib/base.dart deleted file mode 100644 index d57dd72..0000000 --- a/lib/base.dart +++ /dev/null @@ -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 createState() => _BaseState(); -} - -class _BaseState extends State with WidgetsBindingObserver { - int selectedScreen = 0; - - @override - void initState() { - WidgetsBinding.instance.addObserver(this); - - super.initState(); - - WidgetsBinding.instance.addPostFrameCallback((_) async { - final appConfigProvider = Provider.of(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(context); - final appConfigProvider = Provider.of(context); - - final width = MediaQuery.of(context).size.width; - - List screens = serversProvider.selectedServer != null - ? screensServerConnected - : screensSelectServer; - - if (kDebugMode && dotenv.env['ENABLE_SENTRY'] == "true") { - Sentry.captureMessage("Debug mode"); - } - - return CustomMenuBar( - child: AnnotatedRegion( - 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: SizedBox() - ), - ), - ], - ), - bottomNavigationBar: width <= 900 - ? const BottomNavBar() - : null, - ) - ), - ); - } -} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index fafb46e..1548e4a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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,6 +16,10 @@ 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/widgets/menu_bar.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/widgets/update_modal.dart'; import 'package:adguard_home_manager/routes/router.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; @@ -175,7 +180,7 @@ class Main extends StatefulWidget { State
createState() => _MainState(); } -class _MainState extends State
{ +class _MainState extends State
with WidgetsBindingObserver { List modes = []; DisplayMode? active; DisplayMode? preferred; @@ -195,54 +200,93 @@ class _MainState extends State
{ @override void initState() { displayMode(); + + WidgetsBinding.instance.addObserver(this); + super.initState(); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + final appConfigProvider = Provider.of(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 && mounted) { + await showDialog( + context: context, + builder: (context) => UpdateModal( + gitHubRelease: result, + onDownload: (link, version) => openUrl(link), + ), + ); + } + }); } @override Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); - - return DynamicColorBuilder( - builder: (lightDynamic, darkDynamic) => MaterialApp.router( - title: 'AdGuard Home Manager', - theme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 - ? appConfigProvider.useDynamicColor == true - ? lightTheme(lightDynamic) - : lightThemeOldVersions(colors[appConfigProvider.staticColor]) - : lightThemeOldVersions(colors[appConfigProvider.staticColor]), - darkTheme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 - ? appConfigProvider.useDynamicColor == true - ? darkTheme(darkDynamic) - : darkThemeOldVersions(colors[appConfigProvider.staticColor]) - : darkThemeOldVersions(colors[appConfigProvider.staticColor]), - themeMode: appConfigProvider.selectedTheme, - debugShowCheckedModeBanner: false, - localizationsDelegates: const [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - AppLocalizations.delegate, - ], - supportedLocales: const [ - Locale('en', ''), - Locale('es', ''), - Locale('zh', ''), - Locale('zh', 'CN'), - Locale('pl', ''), - Locale('tr', '') - ], - scaffoldMessengerKey: scaffoldMessengerKey, - builder: (context, child) { - return MediaQuery( - data: MediaQuery.of(context).copyWith( - textScaleFactor: !(Platform.isAndroid || Platform.isIOS) - ? 0.9 - : 1.0 - ), - child: child!, - ); - }, - routerConfig: goRouter, + + return AnnotatedRegion( + 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: DynamicColorBuilder( + builder: (lightDynamic, darkDynamic) => MaterialApp.router( + title: 'AdGuard Home Manager', + theme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 + ? appConfigProvider.useDynamicColor == true + ? lightTheme(lightDynamic) + : lightThemeOldVersions(colors[appConfigProvider.staticColor]) + : lightThemeOldVersions(colors[appConfigProvider.staticColor]), + darkTheme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 + ? appConfigProvider.useDynamicColor == true + ? darkTheme(darkDynamic) + : darkThemeOldVersions(colors[appConfigProvider.staticColor]) + : darkThemeOldVersions(colors[appConfigProvider.staticColor]), + themeMode: appConfigProvider.selectedTheme, + debugShowCheckedModeBanner: false, + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + AppLocalizations.delegate, + ], + supportedLocales: const [ + Locale('en', ''), + Locale('es', ''), + Locale('zh', ''), + Locale('zh', 'CN'), + Locale('pl', ''), + Locale('tr', '') + ], + scaffoldMessengerKey: scaffoldMessengerKey, + builder: (context, child) { + return CustomMenuBar( + child: MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaleFactor: !(Platform.isAndroid || Platform.isIOS) + ? 0.9 + : 1.0 + ), + child: child!, + ), + ); + }, + routerConfig: goRouter, + ), ), ); } From 5c50a486c42e7f69e2011315289737fc60fe765f Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 03:04:45 +0100 Subject: [PATCH 043/177] Changes --- lib/routes/routes.dart | 56 +++++++++++++++++++++++++++++---- lib/widgets/bottom_nav_bar.dart | 15 --------- 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart index a216446..9a79603 100644 --- a/lib/routes/routes.dart +++ b/lib/routes/routes.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:adguard_home_manager/screens/home/home.dart'; @@ -8,6 +9,7 @@ import 'package:adguard_home_manager/screens/logs/logs.dart'; import 'package:adguard_home_manager/screens/settings/settings.dart'; import 'package:adguard_home_manager/widgets/layout.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:adguard_home_manager/constants/routes_names.dart'; @@ -27,7 +29,14 @@ final List routes = [ GoRoute( parentNavigatorKey: homeNavigatorKey, path: RoutesNames.home, - builder: (context, state) => const Home(), + pageBuilder: (context, state) { + if (isDesktop(MediaQuery.of(context).size.width)) { + return const NoTransitionPage(child: Home()); + } + else { + return const MaterialPage(child: Home()); + } + }, ), ] ), @@ -37,7 +46,14 @@ final List routes = [ GoRoute( parentNavigatorKey: clientsNavigatorKey, path: RoutesNames.clients, - builder: (context, state) => const Clients(), + pageBuilder: (context, state) { + if (isDesktop(MediaQuery.of(context).size.width)) { + return const NoTransitionPage(child: Clients()); + } + else { + return const MaterialPage(child: Clients()); + } + }, ) ] ), @@ -47,7 +63,14 @@ final List routes = [ GoRoute( parentNavigatorKey: logsNavigatorKey, path: RoutesNames.logs, - builder: (context, state) => const Logs(), + pageBuilder: (context, state) { + if (isDesktop(MediaQuery.of(context).size.width)) { + return const NoTransitionPage(child: Logs()); + } + else { + return const MaterialPage(child: Logs()); + } + }, ) ] ), @@ -57,7 +80,14 @@ final List routes = [ GoRoute( parentNavigatorKey: filtersNavigatorKey, path: RoutesNames.filters, - builder: (context, state) => const Filters(), + pageBuilder: (context, state) { + if (isDesktop(MediaQuery.of(context).size.width)) { + return const NoTransitionPage(child: Filters()); + } + else { + return const MaterialPage(child: Filters()); + } + }, ) ] ), @@ -67,7 +97,14 @@ final List routes = [ GoRoute( parentNavigatorKey: settingsNavigatorKey, path: RoutesNames.settings, - builder: (context, state) => const Settings(), + pageBuilder: (context, state) { + if (isDesktop(MediaQuery.of(context).size.width)) { + return const NoTransitionPage(child: Settings()); + } + else { + return const MaterialPage(child: Settings()); + } + }, ) ] ), @@ -76,7 +113,14 @@ final List routes = [ routes: [ GoRoute( path: RoutesNames.connect, - builder: (context, state) => const Connect(), + pageBuilder: (context, state) { + if (isDesktop(MediaQuery.of(context).size.width)) { + return const NoTransitionPage(child: Connect()); + } + else { + return const MaterialPage(child: Connect()); + } + }, ) ] ), diff --git a/lib/widgets/bottom_nav_bar.dart b/lib/widgets/bottom_nav_bar.dart index 490138c..b5ff9df 100644 --- a/lib/widgets/bottom_nav_bar.dart +++ b/lib/widgets/bottom_nav_bar.dart @@ -82,21 +82,6 @@ class BottomNavBar extends StatelessWidget { ), label: translatedName(screen.name) )).toList(), - // onDestinationSelected: (value) { - // // Reset clients tab to 0 when changing screen - // if (value != 1) { - // appConfigProvider.setSelectedClientsTab(0); - // } - // // Reset logs filters when changing screen - // if (value != 2) { - // logsProvider.resetFilters(); - // } - // // Reset settings selected screen - // if (value != screens.length-1) { - // appConfigProvider.setSelectedSettingsScreen(screen: null); - // } - // appConfigProvider.setSelectedScreen(value); - // }, ); } } \ No newline at end of file From a8bd57904ceeef508500c37aa4a79348bb5d797b Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 03:08:15 +0100 Subject: [PATCH 044/177] Changed size ring chart --- README.md | 2 ++ lib/screens/home/top_items.dart | 2 +- lib/widgets/custom_pie_chart.dart | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 864b3de..4128ac3 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,8 @@ On [this repository](https://github.com/JuanRodenas/Pihole_list) you can find a - [flutter dotenv](https://pub.dev/packages/flutter_dotenv) - [flutter reorderable list](https://pub.dev/packages/flutter_reorderable_list) - [pie chart](https://pub.dev/packages/pie_chart) +- [go router](https://pub.dev/packages/go_router) +- [flutter hooks](https://pub.dev/packages/flutter_hooks)
diff --git a/lib/screens/home/top_items.dart b/lib/screens/home/top_items.dart index 06ef00e..ebf4e94 100644 --- a/lib/screens/home/top_items.dart +++ b/lib/screens/home/top_items.dart @@ -186,7 +186,7 @@ class _TopItemsState extends State { if (widget.data.isNotEmpty && width <= 700) ...[ if (_showChart) ...[ SizedBox( - height: 300, + height: 150, child: chart ), const SizedBox(height: 16), diff --git a/lib/widgets/custom_pie_chart.dart b/lib/widgets/custom_pie_chart.dart index 2968568..6af7e11 100644 --- a/lib/widgets/custom_pie_chart.dart +++ b/lib/widgets/custom_pie_chart.dart @@ -16,7 +16,6 @@ class CustomPieChart extends StatelessWidget { return PieChart( dataMap: data, animationDuration: const Duration(milliseconds: 800), - chartRadius: MediaQuery.of(context).size.width / 3, colorList: colors, initialAngleInDegree: 270, chartType: ChartType.ring, From 79cbb1beae00c20a66e1f07856efdd7608e59d8e Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 13:59:36 +0100 Subject: [PATCH 045/177] Refactor code management modal --- lib/screens/home/combined_chart.dart | 117 +++-- lib/screens/home/fab.dart | 2 +- lib/screens/home/home.dart | 230 ++++----- lib/screens/home/management_modal.dart | 476 ------------------ .../home/management_modal/main_switch.dart | 266 ++++++++++ .../management_modal/management_modal.dart | 269 ++++++++++ .../home/management_modal/small_switch.dart | 65 +++ 7 files changed, 774 insertions(+), 651 deletions(-) delete mode 100644 lib/screens/home/management_modal.dart create mode 100644 lib/screens/home/management_modal/main_switch.dart create mode 100644 lib/screens/home/management_modal/management_modal.dart create mode 100644 lib/screens/home/management_modal/small_switch.dart diff --git a/lib/screens/home/combined_chart.dart b/lib/screens/home/combined_chart.dart index f781b7d..89519db 100644 --- a/lib/screens/home/combined_chart.dart +++ b/lib/screens/home/combined_chart.dart @@ -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 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 + ), + ) + ], + ) + ], + ); + } } \ No newline at end of file diff --git a/lib/screens/home/fab.dart b/lib/screens/home/fab.dart index d0701cc..bd77be7 100644 --- a/lib/screens/home/fab.dart +++ b/lib/screens/home/fab.dart @@ -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'; diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index fcac0cc..293eef3 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -61,124 +61,6 @@ class _HomeState extends State { 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 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)!.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, - ), - ), - 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(), - ), - - TopItemsLists(order: appConfigProvider.homeTopItemsOrder), - ]; - } - return Scaffold( body: SafeArea( top: false, @@ -215,13 +97,119 @@ class _HomeState extends State { 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, + ), + ), + 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)!.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, + ), + ), + 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(), + ), + + 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, + ), + ) + ], + ), + ) ), ], ) diff --git a/lib/screens/home/management_modal.dart b/lib/screens/home/management_modal.dart deleted file mode 100644 index 9b94b58..0000000 --- a/lib/screens/home/management_modal.dart +++ /dev/null @@ -1,476 +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 createState() => _ManagementModalState(); -} - -class _ManagementModalState extends State with SingleTickerProviderStateMixin { - late AnimationController animationController; - late Animation animation; - final ExpandableController expandableController = ExpandableController(); - - final _chipsScrollController = ScrollController(); - - @override - void initState() { - expandableController.addListener(() async { - await Future.delayed(const Duration(milliseconds: 200)); - if (expandableController.value == false) { - animationController.animateTo(0); - } - else { - animationController.animateBack(1); - } - }); - - animationController = AnimationController( - duration: const Duration(milliseconds: 200), - vsync: this, - ) - ..addListener(() => setState(() => {})); - animation = Tween( - begin: 0.0, - end: 0.5, - ).animate(CurvedAnimation( - parent: animationController, - curve: Curves.easeInOut - )); - - super.initState(); - } - - @override - void dispose() { - animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final statusProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - - void updateBlocking({ - required bool value, - required String filter, - int? time - }) async { - final result = await statusProvider.updateBlocking( - block: filter, - newStatus: value, - time: time - ); - if (mounted && result != null) { - if (result != false) { - appConfigProvider.addLog(result); - } - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.invalidUsernamePassword, - color: Colors.red - ); - } - } - - void disableWithCountdown(int time) async { - updateBlocking(value: false, filter: 'general', time: time); - expandableController.toggle(); - } - - Widget mainSwitch() { - Widget topRow({required bool legacyMode}) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - if (legacyMode == false) ...[ - RotationTransition( - turns: animation, - child: Icon( - Icons.keyboard_arrow_down_rounded, - size: 26, - color: statusProvider.serverStatus!.generalEnabled == true - ? Theme.of(context).colorScheme.onSurfaceVariant - : Colors.grey, - ), - ), - const SizedBox(width: 8), - ], - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.allProtections, - style: const TextStyle( - fontSize: 18, - ), - ), - if (statusProvider.serverStatus!.timeGeneralDisabled > 0) ...[ - const SizedBox(height: 2), - if (statusProvider.currentDeadline != null) Text( - "${AppLocalizations.of(context)!.remainingTime}: ${formatRemainingSeconds(statusProvider.remainingTime)}" - ) - ] - ], - ), - ], - ), - Switch( - value: statusProvider.serverStatus!.generalEnabled, - onChanged: statusProvider.protectionsManagementProcess.contains('general') == false - ? (value) { - if (value == false && expandableController.expanded == true && legacyMode == false) { - expandableController.toggle(); - } - updateBlocking( - value: value, - filter: legacyMode == true ? 'general_legacy' : 'general' - ); - } : null, - ) - ] - ); - } - - Widget bottomRow() { - return Container( - height: Platform.isMacOS || Platform.isLinux || Platform.isWindows ? 50 : 40, - 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 - ? () => disableWithCountdown(29000) - : null, - ), - const SizedBox(width: 8), - ActionChip( - label: Text(AppLocalizations.of(context)!.minute(1)), - onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true - ? () => disableWithCountdown(59000) - : null, - ), - const SizedBox(width: 8), - ActionChip( - label: Text(AppLocalizations.of(context)!.minutes(10)), - onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true - ? () => disableWithCountdown(599000) - : null, - ), - const SizedBox(width: 8), - ActionChip( - label: Text(AppLocalizations.of(context)!.hour(1)), - onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true - ? () => disableWithCountdown(3599000) - : null, - ), - const SizedBox(width: 8), - ActionChip( - label: Text(AppLocalizations.of(context)!.hours(24)), - onPressed: statusProvider.protectionsManagementProcess.contains('general') == false && statusProvider.serverStatus!.generalEnabled == true - ? () => disableWithCountdown(86399000) - : null, - ), - ], - ), - ), - ), - ); - } - - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: serverVersionIsAhead( - currentVersion: statusProvider.serverStatus!.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == true - ? ExpandableNotifier( - controller: expandableController, - child: Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(28), - child: InkWell( - onTap: statusProvider.serverStatus!.generalEnabled == true && !statusProvider.protectionsManagementProcess.contains('general') - ? () => expandableController.toggle() - : null, - borderRadius: BorderRadius.circular(28), - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 12 - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(28), - color: Theme.of(context).colorScheme.primary.withOpacity(0.1) - ), - child: Expandable( - theme: const ExpandableThemeData( - animationDuration: Duration(milliseconds: 200), - fadeCurve: Curves.ease - ), - collapsed: topRow(legacyMode: false), - expanded: Column( - children: [ - topRow(legacyMode: false), - bottomRow(), - const SizedBox(height: 8) - ], - ) - ), - ), - ), - ) - ) - : Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(28), - child: InkWell( - onTap: statusProvider.protectionsManagementProcess.contains('general') == false - ? () => updateBlocking( - value: !statusProvider.serverStatus!.generalEnabled, - filter: 'general_legacy' - ) : null, - borderRadius: BorderRadius.circular(28), - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 12 - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(28), - color: Theme.of(context).primaryColor.withOpacity(0.1) - ), - child: topRow(legacyMode: true) - ), - ), - ) - ); - } - - Widget smallSwitch(String label, IconData icon, bool value, Function(bool) onChange, bool disabled) { - return Material( - color: Colors.transparent, - child: InkWell( - onTap: disabled == false - ? () => onChange(!value) - : null, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 44, - vertical: 8 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Icon( - icon, - size: 24, - color: Theme.of(context).listTileTheme.iconColor, - ), - const SizedBox(width: 16), - Text( - label, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - ], - ), - Switch( - value: value, - onChanged: disabled == false - ? onChange - : null, - ) - ], - ), - ), - ), - ); - } - - Widget header() { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.only(top: 24), - child: Icon( - Icons.shield_rounded, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Text( - AppLocalizations.of(context)!.manageServer, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - ), - ], - ), - ], - ); - } - - List toggles() { - return [ - mainSwitch(), - Container(height: 10), - smallSwitch( - AppLocalizations.of(context)!.ruleFiltering, - Icons.filter_list_rounded, - statusProvider.serverStatus!.filteringEnabled, - (value) => updateBlocking(value: value, filter: 'filtering'), - statusProvider.protectionsManagementProcess.contains('filtering') - ), - smallSwitch( - AppLocalizations.of(context)!.safeBrowsing, - Icons.vpn_lock_rounded, - statusProvider.serverStatus!.safeBrowsingEnabled, - (value) => updateBlocking(value: value, filter: 'safeBrowsing'), - statusProvider.protectionsManagementProcess.contains('safeBrowsing') - ), - smallSwitch( - AppLocalizations.of(context)!.parentalFiltering, - Icons.block, - statusProvider.serverStatus!.parentalControlEnabled, - (value) => updateBlocking(value: value, filter: 'parentalControl'), - statusProvider.protectionsManagementProcess.contains('parentalControl') - ), - smallSwitch( - AppLocalizations.of(context)!.safeSearch, - Icons.search_rounded, - statusProvider.serverStatus!.safeSearchEnabled, - (value) => updateBlocking(value: value, filter: 'safeSearch'), - statusProvider.protectionsManagementProcess.contains('safeSearch') - ), - ]; - } - - if (widget.dialog == true) { - return Dialog( - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 400 - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: SingleChildScrollView( - child: Wrap( - children: [ - header(), - ...toggles() - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.all(24), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.close), - ), - ], - ), - ), - if (Platform.isIOS) const SizedBox(height: 16) - ], - ), - ), - ); - } - else { - return Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28) - ) - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: SingleChildScrollView( - child: Wrap( - children: [ - header(), - ...toggles() - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.all(24), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.close), - ), - ], - ), - ), - if (Platform.isIOS) const SizedBox(height: 16) - ], - ), - ); - } - } -} \ No newline at end of file diff --git a/lib/screens/home/management_modal/main_switch.dart b/lib/screens/home/management_modal/main_switch.dart new file mode 100644 index 0000000..6c7b2ae --- /dev/null +++ b/lib/screens/home/management_modal/main_switch.dart @@ -0,0 +1,266 @@ +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/functions/compare_versions.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 animation; + + const MainSwitch({ + Key? key, + required this.expandableController, + required this.updateBlocking, + required this.disableWithCountdown, + required this.animation, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final statusProvider = Provider.of(context); + + 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, + 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) + ], + ) + ), + ), + ), + ) + ) + : 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, + expandableController: expandableController, + updateBlocking: updateBlocking, + animation: animation, + ) + ), + ), + ) + ); + } +} + +class _TopRow extends StatelessWidget { + final bool legacyMode; + final ExpandableController expandableController; + final void Function({ required bool value, required String filter }) updateBlocking; + final Animation animation; + + const _TopRow({ + Key? key, + required this.legacyMode, + required this.expandableController, + required this.updateBlocking, + required this.animation, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final statusProvider = Provider.of(context); + + 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, + ) + ] + ); + } +} + +class _BottomRow extends StatefulWidget { + final void Function(int) disableWithCountdown; + + const _BottomRow({ + Key? key, + required this.disableWithCountdown, + }) : super(key: key); + + @override + State<_BottomRow> createState() => _BottomRowState(); +} + +class _BottomRowState extends State<_BottomRow> { + final _chipsScrollController = ScrollController(); + + @override + Widget build(BuildContext context) { + final statusProvider = Provider.of(context); + + return Container( + height: Platform.isMacOS || Platform.isLinux || Platform.isWindows ? 50 : 40, + 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, + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/home/management_modal/management_modal.dart b/lib/screens/home/management_modal/management_modal.dart new file mode 100644 index 0000000..8fa968c --- /dev/null +++ b/lib/screens/home/management_modal/management_modal.dart @@ -0,0 +1,269 @@ +// 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({ + Key? key, + required this.dialog + }) : super(key: key); + + @override + State createState() => _ManagementModalState(); +} + +class _ManagementModalState extends State with SingleTickerProviderStateMixin { + late AnimationController animationController; + late Animation animation; + final ExpandableController expandableController = ExpandableController(); + + @override + void initState() { + expandableController.addListener(() async { + await Future.delayed(const Duration(milliseconds: 200)); + if (expandableController.value == false) { + animationController.animateTo(0); + } + else { + animationController.animateBack(1); + } + }); + + animationController = AnimationController( + duration: const Duration(milliseconds: 200), + vsync: this, + ) + ..addListener(() => setState(() => {})); + animation = Tween( + begin: 0.0, + end: 0.5, + ).animate(CurvedAnimation( + parent: animationController, + curve: Curves.easeInOut + )); + + super.initState(); + } + + @override + void dispose() { + animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final statusProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); + + void updateBlocking({ + required bool value, + required String filter, + int? time + }) async { + final result = await statusProvider.updateBlocking( + block: filter, + newStatus: value, + time: time + ); + if (mounted && result != null) { + if (result != false) { + appConfigProvider.addLog(result); + } + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.invalidUsernamePassword, + color: Colors.red + ); + } + } + + void disableWithCountdown(int time) async { + updateBlocking(value: false, filter: 'general', time: time); + expandableController.toggle(); + } + + 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: 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 animation; + + const _Modal({ + Key? key, + required this.expandableController, + required this.updateBlocking, + required this.disableWithCountdown, + required this.animation, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final statusProvider = Provider.of(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') + ), + ], + ); + } +} diff --git a/lib/screens/home/management_modal/small_switch.dart b/lib/screens/home/management_modal/small_switch.dart new file mode 100644 index 0000000..4e34d68 --- /dev/null +++ b/lib/screens/home/management_modal/small_switch.dart @@ -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, + ) + ], + ), + ), + ), + ); + } +} \ No newline at end of file From c391c76f5ab2c459d027144dc0f3e6fe7b1864d8 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 14:31:11 +0100 Subject: [PATCH 046/177] Navigate with root key --- lib/routes/routes.dart | 92 +++++++++---------- .../clients/client/logs_list_client.dart | 13 ++- lib/screens/clients/clients_lists.dart | 17 ++-- lib/screens/filters/filters.dart | 3 +- lib/screens/home/appbar.dart | 5 +- lib/screens/home/top_items.dart | 3 +- lib/screens/logs/logs_list.dart | 13 ++- lib/screens/settings/dhcp/dhcp.dart | 49 ++++++---- lib/screens/settings/dns/dns.dart | 9 +- .../general_settings/general_settings.dart | 9 +- lib/screens/settings/settings.dart | 3 +- 11 files changed, 121 insertions(+), 95 deletions(-) diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart index 9a79603..351906a 100644 --- a/lib/routes/routes.dart +++ b/lib/routes/routes.dart @@ -13,6 +13,26 @@ import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:adguard_home_manager/constants/routes_names.dart'; +Page routePage({ + required BuildContext context, + required Widget child +}) { + if (isDesktop(MediaQuery.of(context).size.width)) { + return NoTransitionPage(child: child); + } + else { + return CustomTransitionPage( + child: child, + transitionsBuilder: (context, anim1, anim2, child) { + return FadeTransition( + opacity: anim1, + child: child, + ); + }, + ); + } +} + final List routes = [ GoRoute( path: "/", @@ -29,14 +49,10 @@ final List routes = [ GoRoute( parentNavigatorKey: homeNavigatorKey, path: RoutesNames.home, - pageBuilder: (context, state) { - if (isDesktop(MediaQuery.of(context).size.width)) { - return const NoTransitionPage(child: Home()); - } - else { - return const MaterialPage(child: Home()); - } - }, + pageBuilder: (context, state) => routePage( + context: context, + child: const Home() + ) ), ] ), @@ -46,14 +62,10 @@ final List routes = [ GoRoute( parentNavigatorKey: clientsNavigatorKey, path: RoutesNames.clients, - pageBuilder: (context, state) { - if (isDesktop(MediaQuery.of(context).size.width)) { - return const NoTransitionPage(child: Clients()); - } - else { - return const MaterialPage(child: Clients()); - } - }, + pageBuilder: (context, state) => routePage( + context: context, + child: const Clients() + ) ) ] ), @@ -63,14 +75,10 @@ final List routes = [ GoRoute( parentNavigatorKey: logsNavigatorKey, path: RoutesNames.logs, - pageBuilder: (context, state) { - if (isDesktop(MediaQuery.of(context).size.width)) { - return const NoTransitionPage(child: Logs()); - } - else { - return const MaterialPage(child: Logs()); - } - }, + pageBuilder: (context, state) => routePage( + context: context, + child: const Logs() + ) ) ] ), @@ -80,14 +88,10 @@ final List routes = [ GoRoute( parentNavigatorKey: filtersNavigatorKey, path: RoutesNames.filters, - pageBuilder: (context, state) { - if (isDesktop(MediaQuery.of(context).size.width)) { - return const NoTransitionPage(child: Filters()); - } - else { - return const MaterialPage(child: Filters()); - } - }, + pageBuilder: (context, state) => routePage( + context: context, + child: const Filters() + ) ) ] ), @@ -97,14 +101,10 @@ final List routes = [ GoRoute( parentNavigatorKey: settingsNavigatorKey, path: RoutesNames.settings, - pageBuilder: (context, state) { - if (isDesktop(MediaQuery.of(context).size.width)) { - return const NoTransitionPage(child: Settings()); - } - else { - return const MaterialPage(child: Settings()); - } - }, + pageBuilder: (context, state) => routePage( + context: context, + child: const Settings() + ) ) ] ), @@ -113,14 +113,10 @@ final List routes = [ routes: [ GoRoute( path: RoutesNames.connect, - pageBuilder: (context, state) { - if (isDesktop(MediaQuery.of(context).size.width)) { - return const NoTransitionPage(child: Connect()); - } - else { - return const MaterialPage(child: Connect()); - } - }, + pageBuilder: (context, state) => routePage( + context: context, + child: const Connect() + ) ) ] ), diff --git a/lib/screens/clients/client/logs_list_client.dart b/lib/screens/clients/client/logs_list_client.dart index 5d8f6ed..fa95e60 100644 --- a/lib/screens/clients/client/logs_list_client.dart +++ b/lib/screens/clients/client/logs_list_client.dart @@ -8,6 +8,7 @@ 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/routes/router_globals.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'; @@ -212,12 +213,14 @@ class _LogsListClientState extends State { ) } else { - Navigator.push(context, MaterialPageRoute( - builder: (context) => LogDetailsScreen( - log: log, - dialog: false + rootNavigatorKey.currentState!.push( + MaterialPageRoute( + builder: (context) => LogDetailsScreen( + log: log, + dialog: false + ) ) - )) + ) } }, twoColumns: widget.splitView, diff --git a/lib/screens/clients/clients_lists.dart b/lib/screens/clients/clients_lists.dart index ec451bc..47e7d12 100644 --- a/lib/screens/clients/clients_lists.dart +++ b/lib/screens/clients/clients_lists.dart @@ -8,6 +8,7 @@ import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dar import 'package:adguard_home_manager/screens/clients/clients_list.dart'; import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/routes/router_globals.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'; @@ -71,9 +72,11 @@ class _ClientsListsState extends State with TickerProviderStateMix SplitView.of(context).push(w); } else { - Navigator.push(context, MaterialPageRoute( - builder: (context) => w, - )); + rootNavigatorKey.currentState!.push( + MaterialPageRoute( + builder: (context) => w, + ) + ); } } @@ -89,9 +92,11 @@ class _ClientsListsState extends State with TickerProviderStateMix SplitView.of(context).push(w); } else { - Navigator.push(context, MaterialPageRoute( - builder: (context) => w, - )); + rootNavigatorKey.currentState!.push( + MaterialPageRoute( + builder: (context) => w, + ) + ); } } diff --git a/lib/screens/filters/filters.dart b/lib/screens/filters/filters.dart index 5975cf6..919f22c 100644 --- a/lib/screens/filters/filters.dart +++ b/lib/screens/filters/filters.dart @@ -14,6 +14,7 @@ import 'package:adguard_home_manager/screens/filters/remove_custom_rule_modal.da 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/routes/router_globals.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -202,7 +203,7 @@ class _FiltersState extends State { ); } else { - Navigator.of(context).push( + rootNavigatorKey.currentState!.push( MaterialPageRoute( builder: (context) => ListDetailsScreen( listId: filter.id, diff --git a/lib/screens/home/appbar.dart b/lib/screens/home/appbar.dart index 34893b6..2bccc66 100644 --- a/lib/screens/home/appbar.dart +++ b/lib/screens/home/appbar.dart @@ -1,10 +1,11 @@ -import 'package:adguard_home_manager/functions/desktop_mode.dart'; 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/routes/router_globals.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'; @@ -32,7 +33,7 @@ class HomeAppBar extends StatelessWidget { void navigateServers() { Future.delayed(const Duration(milliseconds: 0), (() { - Navigator.of(context).push( + rootNavigatorKey.currentState!.push( MaterialPageRoute(builder: (context) => const Servers()) ); })); diff --git a/lib/screens/home/top_items.dart b/lib/screens/home/top_items.dart index 06ef00e..dce0f52 100644 --- a/lib/screens/home/top_items.dart +++ b/lib/screens/home/top_items.dart @@ -11,6 +11,7 @@ import 'package:adguard_home_manager/widgets/domain_options.dart'; import 'package:adguard_home_manager/screens/top_items/top_items_modal.dart'; import 'package:adguard_home_manager/screens/top_items/top_items.dart'; +import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:adguard_home_manager/models/applied_filters.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; @@ -217,7 +218,7 @@ class _TopItemsState extends State { ) } else { - Navigator.of(context).push( + rootNavigatorKey.currentState!.push( MaterialPageRoute( builder: (context) => TopItemsScreen( type: widget.type, diff --git a/lib/screens/logs/logs_list.dart b/lib/screens/logs/logs_list.dart index 6fd80e6..fa1aed2 100644 --- a/lib/screens/logs/logs_list.dart +++ b/lib/screens/logs/logs_list.dart @@ -12,6 +12,7 @@ import 'package:adguard_home_manager/screens/logs/logs_config_modal.dart'; import 'package:adguard_home_manager/screens/logs/logs_filters_modal.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; +import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/functions/compare_versions.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; @@ -463,12 +464,14 @@ class _LogsListWidgetState extends State { isLogSelected: widget.selectedLog != null && widget.selectedLog == logsProvider.logsData!.data[index], onLogTap: (log) { if (!widget.twoColumns) { - Navigator.push(context, MaterialPageRoute( - builder: (context) => LogDetailsScreen( - log: log, - dialog: false, + rootNavigatorKey.currentState!.push( + MaterialPageRoute( + builder: (context) => LogDetailsScreen( + log: log, + dialog: false, + ) ) - )); + ); } widget.onLogSelected(log); }, diff --git a/lib/screens/settings/dhcp/dhcp.dart b/lib/screens/settings/dhcp/dhcp.dart index aa44414..ccd50e7 100644 --- a/lib/screens/settings/dhcp/dhcp.dart +++ b/lib/screens/settings/dhcp/dhcp.dart @@ -12,6 +12,7 @@ import 'package:adguard_home_manager/widgets/confirm_action_modal.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_leases.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/select_interface_modal.dart'; +import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/constants/enums.dart'; @@ -705,12 +706,14 @@ class _DhcpScreenState extends State { color: Colors.transparent, child: InkWell( onTap: () { - Navigator.push(context, MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.leases, - staticLeases: false, + rootNavigatorKey.currentState!.push( + MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.leases, + staticLeases: false, + ) ) - )); + ); }, child: Container( padding: const EdgeInsets.all(16), @@ -738,12 +741,14 @@ class _DhcpScreenState extends State { color: Colors.transparent, child: InkWell( onTap: () { - Navigator.push(context, MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, - staticLeases: true, + rootNavigatorKey.currentState!.push( + MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, + staticLeases: true, + ) ) - )); + ); }, child: Container( padding: const EdgeInsets.all(16), @@ -781,12 +786,14 @@ class _DhcpScreenState extends State { ); } else { - Navigator.push(context, MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.leases, - staticLeases: false, + rootNavigatorKey.currentState!.push( + MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.leases, + staticLeases: false, + ) ) - )); + ); } }, child: Row( @@ -808,12 +815,14 @@ class _DhcpScreenState extends State { ); } else { - Navigator.push(context, MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, - staticLeases: true, + rootNavigatorKey.currentState!.push( + MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, + staticLeases: true, + ) ) - )); + ); } }, child: Row( diff --git a/lib/screens/settings/dns/dns.dart b/lib/screens/settings/dns/dns.dart index 7d0760b..063b9a3 100644 --- a/lib/screens/settings/dns/dns.dart +++ b/lib/screens/settings/dns/dns.dart @@ -14,6 +14,7 @@ import 'package:adguard_home_manager/screens/settings/dns/upstream_dns.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/providers/dns_provider.dart'; import 'package:adguard_home_manager/functions/clear_dns_cache.dart'; @@ -53,9 +54,11 @@ class _DnsSettingsState extends State { SplitView.of(context).push(w); } else { - Navigator.push(context, MaterialPageRoute( - builder: (context) => w - )); + rootNavigatorKey.currentState!.push( + MaterialPageRoute( + builder: (context) => w + ) + ); } } diff --git a/lib/screens/settings/general_settings/general_settings.dart b/lib/screens/settings/general_settings/general_settings.dart index cef6a8f..e91156b 100644 --- a/lib/screens/settings/general_settings/general_settings.dart +++ b/lib/screens/settings/general_settings/general_settings.dart @@ -12,6 +12,7 @@ import 'package:adguard_home_manager/screens/settings/general_settings/reorderab import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; +import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:adguard_home_manager/functions/check_app_updates.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; @@ -191,9 +192,11 @@ class _GeneralSettingsState extends State { icon: Icons.reorder_rounded, title: AppLocalizations.of(context)!.topItemsOrder, subtitle: AppLocalizations.of(context)!.topItemsOrderDescription, - onTap: () => Navigator.push(context, MaterialPageRoute( - builder: (context) => const ReorderableTopItemsHome() - )), + onTap: () => rootNavigatorKey.currentState!.push( + MaterialPageRoute( + builder: (context) => const ReorderableTopItemsHome() + ) + ) ), CustomListTile( icon: Icons.donut_large_rounded, diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index 0f01b44..ce6ea5f 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -23,6 +23,7 @@ import 'package:adguard_home_manager/widgets/custom_settings_tile.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/routes/router_globals.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/constants/strings.dart'; import 'package:adguard_home_manager/functions/open_url.dart'; @@ -119,7 +120,7 @@ class SettingsWidget extends StatelessWidget { icon: icon, trailing: trailing, onTap: () { - Navigator.of(context).push( + rootNavigatorKey.currentState!.push( MaterialPageRoute(builder: (context) => screenToNavigate) ); }, From af14484a9512fc6f7c9c349cad269f093d040b28 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 15:38:17 +0100 Subject: [PATCH 047/177] Removed go router --- lib/config/app_screens.dart | 14 ++ lib/main.dart | 7 +- lib/models/app_screen.dart | 4 +- lib/routes/router.dart | 20 --- lib/routes/router_globals.dart | 10 -- lib/routes/routes.dart | 125 ------------------ .../clients/client/logs_list_client.dart | 3 +- lib/screens/clients/clients_lists.dart | 5 +- lib/screens/filters/filters.dart | 3 +- lib/screens/home/appbar.dart | 3 +- lib/screens/home/top_items.dart | 3 +- lib/screens/logs/logs_list.dart | 3 +- lib/screens/settings/dhcp/dhcp.dart | 9 +- lib/screens/settings/dns/dns.dart | 3 +- .../general_settings/general_settings.dart | 3 +- lib/screens/settings/settings.dart | 22 ++- lib/widgets/layout.dart | 52 +++++--- pubspec.lock | 16 --- pubspec.yaml | 1 - 19 files changed, 85 insertions(+), 221 deletions(-) delete mode 100644 lib/routes/router.dart delete mode 100644 lib/routes/router_globals.dart delete mode 100644 lib/routes/routes.dart diff --git a/lib/config/app_screens.dart b/lib/config/app_screens.dart index 4f9a97d..2326e31 100644 --- a/lib/config/app_screens.dart +++ b/lib/config/app_screens.dart @@ -1,15 +1,24 @@ import 'package:flutter/material.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'; List screensSelectServer = [ const AppScreen( name: "connect", icon: Icons.link_rounded, + child: Connect() ), const AppScreen( name: "settings", icon: Icons.settings_rounded, + child: Settings() ) ]; @@ -17,21 +26,26 @@ List screensServerConnected = [ const AppScreen( name: "home", icon: Icons.home_rounded, + child: Home() ), const AppScreen( name: "clients", icon: Icons.devices, + child: Clients() ), const AppScreen( name: "logs", icon: Icons.list_alt_rounded, + child: Logs() ), const AppScreen( name: "filters", icon: Icons.shield_rounded, + child: Filters() ), const AppScreen( name: "settings", icon: Icons.settings_rounded, + child: Settings() ) ]; \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 1548e4a..f34459f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,11 +16,12 @@ 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/widgets/layout.dart'; import 'package:adguard_home_manager/widgets/menu_bar.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/widgets/update_modal.dart'; -import 'package:adguard_home_manager/routes/router.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; @@ -244,7 +245,7 @@ class _MainState extends State
with WidgetsBindingObserver { : Brightness.light, ), child: DynamicColorBuilder( - builder: (lightDynamic, darkDynamic) => MaterialApp.router( + builder: (lightDynamic, darkDynamic) => MaterialApp( title: 'AdGuard Home Manager', theme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 ? appConfigProvider.useDynamicColor == true @@ -285,7 +286,7 @@ class _MainState extends State
with WidgetsBindingObserver { ), ); }, - routerConfig: goRouter, + home: Layout(), ), ), ); diff --git a/lib/models/app_screen.dart b/lib/models/app_screen.dart index 1e377eb..4412022 100644 --- a/lib/models/app_screen.dart +++ b/lib/models/app_screen.dart @@ -5,11 +5,13 @@ class AppScreen { final IconData icon; final PreferredSizeWidget? appBar; final Widget? fab; + final Widget child; const AppScreen({ required this.name, required this.icon, this.appBar, - this.fab + this.fab, + required this.child, }); } \ No newline at end of file diff --git a/lib/routes/router.dart b/lib/routes/router.dart deleted file mode 100644 index 9b1cfba..0000000 --- a/lib/routes/router.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:go_router/go_router.dart'; -import 'package:provider/provider.dart'; - -import 'package:adguard_home_manager/routes/router_globals.dart'; -import 'package:adguard_home_manager/routes/routes.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; -import 'package:adguard_home_manager/constants/routes_names.dart'; - -final goRouter = GoRouter( - navigatorKey: rootNavigatorKey, - redirect: (context, state) { - final serversProvider = Provider.of(context, listen: false); - if (serversProvider.selectedServer == null) { - return RoutesNames.connect; - } - return null; - }, - initialLocation: RoutesNames.home, - routes: routes, -); \ No newline at end of file diff --git a/lib/routes/router_globals.dart b/lib/routes/router_globals.dart deleted file mode 100644 index f78c1a7..0000000 --- a/lib/routes/router_globals.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter/widgets.dart'; - -final GlobalKey rootNavigatorKey = GlobalKey(); -final GlobalKey connectNavigatorKey = GlobalKey(); -final GlobalKey homeNavigatorKey = GlobalKey(); -final GlobalKey clientsNavigatorKey = GlobalKey(); -final GlobalKey logsNavigatorKey = GlobalKey(); -final GlobalKey filtersNavigatorKey = GlobalKey(); -final GlobalKey settingsNavigatorKey = GlobalKey(); -final GlobalKey settingsListNavigatorKey = GlobalKey(); \ No newline at end of file diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart deleted file mode 100644 index 351906a..0000000 --- a/lib/routes/routes.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.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/logs/logs.dart'; -import 'package:adguard_home_manager/screens/settings/settings.dart'; -import 'package:adguard_home_manager/widgets/layout.dart'; - -import 'package:adguard_home_manager/functions/desktop_mode.dart'; -import 'package:adguard_home_manager/routes/router_globals.dart'; -import 'package:adguard_home_manager/constants/routes_names.dart'; - -Page routePage({ - required BuildContext context, - required Widget child -}) { - if (isDesktop(MediaQuery.of(context).size.width)) { - return NoTransitionPage(child: child); - } - else { - return CustomTransitionPage( - child: child, - transitionsBuilder: (context, anim1, anim2, child) { - return FadeTransition( - opacity: anim1, - child: child, - ); - }, - ); - } -} - -final List routes = [ - GoRoute( - path: "/", - redirect: (context, state) => RoutesNames.home, - ), - StatefulShellRoute.indexedStack( - builder: (context, state, navigationShell) => Layout( - navigationShell: navigationShell - ), - branches: [ - StatefulShellBranch( - navigatorKey: homeNavigatorKey, - routes: [ - GoRoute( - parentNavigatorKey: homeNavigatorKey, - path: RoutesNames.home, - pageBuilder: (context, state) => routePage( - context: context, - child: const Home() - ) - ), - ] - ), - StatefulShellBranch( - navigatorKey: clientsNavigatorKey, - routes: [ - GoRoute( - parentNavigatorKey: clientsNavigatorKey, - path: RoutesNames.clients, - pageBuilder: (context, state) => routePage( - context: context, - child: const Clients() - ) - ) - ] - ), - StatefulShellBranch( - navigatorKey: logsNavigatorKey, - routes: [ - GoRoute( - parentNavigatorKey: logsNavigatorKey, - path: RoutesNames.logs, - pageBuilder: (context, state) => routePage( - context: context, - child: const Logs() - ) - ) - ] - ), - StatefulShellBranch( - navigatorKey: filtersNavigatorKey, - routes: [ - GoRoute( - parentNavigatorKey: filtersNavigatorKey, - path: RoutesNames.filters, - pageBuilder: (context, state) => routePage( - context: context, - child: const Filters() - ) - ) - ] - ), - StatefulShellBranch( - navigatorKey: settingsNavigatorKey, - routes: [ - GoRoute( - parentNavigatorKey: settingsNavigatorKey, - path: RoutesNames.settings, - pageBuilder: (context, state) => routePage( - context: context, - child: const Settings() - ) - ) - ] - ), - StatefulShellBranch( - navigatorKey: connectNavigatorKey, - routes: [ - GoRoute( - path: RoutesNames.connect, - pageBuilder: (context, state) => routePage( - context: context, - child: const Connect() - ) - ) - ] - ), - ] - ) -]; \ No newline at end of file diff --git a/lib/screens/clients/client/logs_list_client.dart b/lib/screens/clients/client/logs_list_client.dart index fa95e60..9761e3f 100644 --- a/lib/screens/clients/client/logs_list_client.dart +++ b/lib/screens/clients/client/logs_list_client.dart @@ -8,7 +8,6 @@ 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/routes/router_globals.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'; @@ -213,7 +212,7 @@ class _LogsListClientState extends State { ) } else { - rootNavigatorKey.currentState!.push( + Navigator.of(context).push( MaterialPageRoute( builder: (context) => LogDetailsScreen( log: log, diff --git a/lib/screens/clients/clients_lists.dart b/lib/screens/clients/clients_lists.dart index 47e7d12..0e518ce 100644 --- a/lib/screens/clients/clients_lists.dart +++ b/lib/screens/clients/clients_lists.dart @@ -8,7 +8,6 @@ import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dar import 'package:adguard_home_manager/screens/clients/clients_list.dart'; import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/routes/router_globals.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'; @@ -72,7 +71,7 @@ class _ClientsListsState extends State with TickerProviderStateMix SplitView.of(context).push(w); } else { - rootNavigatorKey.currentState!.push( + Navigator.of(context).push( MaterialPageRoute( builder: (context) => w, ) @@ -92,7 +91,7 @@ class _ClientsListsState extends State with TickerProviderStateMix SplitView.of(context).push(w); } else { - rootNavigatorKey.currentState!.push( + Navigator.of(context).push( MaterialPageRoute( builder: (context) => w, ) diff --git a/lib/screens/filters/filters.dart b/lib/screens/filters/filters.dart index 919f22c..5975cf6 100644 --- a/lib/screens/filters/filters.dart +++ b/lib/screens/filters/filters.dart @@ -14,7 +14,6 @@ import 'package:adguard_home_manager/screens/filters/remove_custom_rule_modal.da 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/routes/router_globals.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -203,7 +202,7 @@ class _FiltersState extends State { ); } else { - rootNavigatorKey.currentState!.push( + Navigator.of(context).push( MaterialPageRoute( builder: (context) => ListDetailsScreen( listId: filter.id, diff --git a/lib/screens/home/appbar.dart b/lib/screens/home/appbar.dart index 2bccc66..365e606 100644 --- a/lib/screens/home/appbar.dart +++ b/lib/screens/home/appbar.dart @@ -5,7 +5,6 @@ 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/routes/router_globals.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'; @@ -33,7 +32,7 @@ class HomeAppBar extends StatelessWidget { void navigateServers() { Future.delayed(const Duration(milliseconds: 0), (() { - rootNavigatorKey.currentState!.push( + Navigator.of(context).push( MaterialPageRoute(builder: (context) => const Servers()) ); })); diff --git a/lib/screens/home/top_items.dart b/lib/screens/home/top_items.dart index dce0f52..06ef00e 100644 --- a/lib/screens/home/top_items.dart +++ b/lib/screens/home/top_items.dart @@ -11,7 +11,6 @@ import 'package:adguard_home_manager/widgets/domain_options.dart'; import 'package:adguard_home_manager/screens/top_items/top_items_modal.dart'; import 'package:adguard_home_manager/screens/top_items/top_items.dart'; -import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:adguard_home_manager/models/applied_filters.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; @@ -218,7 +217,7 @@ class _TopItemsState extends State { ) } else { - rootNavigatorKey.currentState!.push( + Navigator.of(context).push( MaterialPageRoute( builder: (context) => TopItemsScreen( type: widget.type, diff --git a/lib/screens/logs/logs_list.dart b/lib/screens/logs/logs_list.dart index fa1aed2..b04fe74 100644 --- a/lib/screens/logs/logs_list.dart +++ b/lib/screens/logs/logs_list.dart @@ -12,7 +12,6 @@ import 'package:adguard_home_manager/screens/logs/logs_config_modal.dart'; import 'package:adguard_home_manager/screens/logs/logs_filters_modal.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; -import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/functions/compare_versions.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; @@ -464,7 +463,7 @@ class _LogsListWidgetState extends State { isLogSelected: widget.selectedLog != null && widget.selectedLog == logsProvider.logsData!.data[index], onLogTap: (log) { if (!widget.twoColumns) { - rootNavigatorKey.currentState!.push( + Navigator.of(context).push( MaterialPageRoute( builder: (context) => LogDetailsScreen( log: log, diff --git a/lib/screens/settings/dhcp/dhcp.dart b/lib/screens/settings/dhcp/dhcp.dart index ccd50e7..c003ea2 100644 --- a/lib/screens/settings/dhcp/dhcp.dart +++ b/lib/screens/settings/dhcp/dhcp.dart @@ -12,7 +12,6 @@ import 'package:adguard_home_manager/widgets/confirm_action_modal.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_leases.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/select_interface_modal.dart'; -import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/constants/enums.dart'; @@ -706,7 +705,7 @@ class _DhcpScreenState extends State { color: Colors.transparent, child: InkWell( onTap: () { - rootNavigatorKey.currentState!.push( + Navigator.of(context).push( MaterialPageRoute( builder: (context) => DhcpLeases( items: dhcpProvider.dhcp!.dhcpStatus.leases, @@ -741,7 +740,7 @@ class _DhcpScreenState extends State { color: Colors.transparent, child: InkWell( onTap: () { - rootNavigatorKey.currentState!.push( + Navigator.of(context).push( MaterialPageRoute( builder: (context) => DhcpLeases( items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, @@ -786,7 +785,7 @@ class _DhcpScreenState extends State { ); } else { - rootNavigatorKey.currentState!.push( + Navigator.of(context).push( MaterialPageRoute( builder: (context) => DhcpLeases( items: dhcpProvider.dhcp!.dhcpStatus.leases, @@ -815,7 +814,7 @@ class _DhcpScreenState extends State { ); } else { - rootNavigatorKey.currentState!.push( + Navigator.of(context).push( MaterialPageRoute( builder: (context) => DhcpLeases( items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, diff --git a/lib/screens/settings/dns/dns.dart b/lib/screens/settings/dns/dns.dart index 063b9a3..1f74669 100644 --- a/lib/screens/settings/dns/dns.dart +++ b/lib/screens/settings/dns/dns.dart @@ -14,7 +14,6 @@ import 'package:adguard_home_manager/screens/settings/dns/upstream_dns.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/providers/dns_provider.dart'; import 'package:adguard_home_manager/functions/clear_dns_cache.dart'; @@ -54,7 +53,7 @@ class _DnsSettingsState extends State { SplitView.of(context).push(w); } else { - rootNavigatorKey.currentState!.push( + Navigator.of(context).push( MaterialPageRoute( builder: (context) => w ) diff --git a/lib/screens/settings/general_settings/general_settings.dart b/lib/screens/settings/general_settings/general_settings.dart index e91156b..4625136 100644 --- a/lib/screens/settings/general_settings/general_settings.dart +++ b/lib/screens/settings/general_settings/general_settings.dart @@ -12,7 +12,6 @@ import 'package:adguard_home_manager/screens/settings/general_settings/reorderab import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; -import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:adguard_home_manager/functions/check_app_updates.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; @@ -192,7 +191,7 @@ class _GeneralSettingsState extends State { icon: Icons.reorder_rounded, title: AppLocalizations.of(context)!.topItemsOrder, subtitle: AppLocalizations.of(context)!.topItemsOrderDescription, - onTap: () => rootNavigatorKey.currentState!.push( + onTap: () => Navigator.of(context).push( MaterialPageRoute( builder: (context) => const ReorderableTopItemsHome() ) diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index ce6ea5f..6a67d28 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -23,7 +23,6 @@ import 'package:adguard_home_manager/widgets/custom_settings_tile.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/routes/router_globals.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/constants/strings.dart'; import 'package:adguard_home_manager/functions/open_url.dart'; @@ -71,7 +70,7 @@ class Settings extends StatelessWidget { ); } } -class SettingsWidget extends StatelessWidget { +class SettingsWidget extends StatefulWidget { final bool twoColumns; const SettingsWidget({ @@ -79,6 +78,17 @@ class SettingsWidget extends StatelessWidget { required this.twoColumns, }) : super(key: key); + @override + State createState() => _SettingsWidgetState(); +} + +class _SettingsWidgetState extends State { + @override + void initState() { + Provider.of(context, listen: false).setSelectedSettingsScreen(screen: null); + super.initState(); + } + @override Widget build(BuildContext context) { final serversProvider = Provider.of(context); @@ -87,7 +97,7 @@ class SettingsWidget extends StatelessWidget { final width = MediaQuery.of(context).size.width; - if (!twoColumns && appConfigProvider.selectedSettingsScreen != null) { + if (!widget.twoColumns && appConfigProvider.selectedSettingsScreen != null) { appConfigProvider.setSelectedSettingsScreen(screen: null); } @@ -99,7 +109,7 @@ class SettingsWidget extends StatelessWidget { required Widget screenToNavigate, required int thisItem }) { - if (twoColumns) { + if (widget.twoColumns) { return CustomSettingsTile( title: title, subtitle: subtitle, @@ -120,7 +130,7 @@ class SettingsWidget extends StatelessWidget { icon: icon, trailing: trailing, onTap: () { - rootNavigatorKey.currentState!.push( + Navigator.of(context).push( MaterialPageRoute(builder: (context) => screenToNavigate) ); }, @@ -191,7 +201,7 @@ class SettingsWidget extends StatelessWidget { subtitle: AppLocalizations.of(context)!.dnsSettingsDescription, thisItem: 3, screenToNavigate: DnsSettings( - splitView: twoColumns, + splitView: widget.twoColumns, ), ), settingsTile( diff --git a/lib/widgets/layout.dart b/lib/widgets/layout.dart index 6104b6f..a7da996 100644 --- a/lib/widgets/layout.dart +++ b/lib/widgets/layout.dart @@ -1,18 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:animations/animations.dart'; import 'package:provider/provider.dart'; -import 'package:go_router/go_router.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/config/app_screens.dart'; import 'package:adguard_home_manager/config/sizes.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; class Layout extends StatefulWidget { - final StatefulNavigationShell navigationShell; - const Layout({ Key? key, - required this.navigationShell, }) : super(key: key); @override @@ -23,10 +21,7 @@ class _LayoutState extends State { bool _drawerExpanded = true; void _goBranch(int index) { - widget.navigationShell.goBranch( - index, - initialLocation: index == widget.navigationShell.currentIndex, - ); + Provider.of(context, listen: false).setSelectedScreen(index); } @override @@ -34,6 +29,11 @@ class _LayoutState extends State { final width = MediaQuery.of(context).size.width; final serversProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); + + final screens = serversProvider.selectedServer != null + ? screensServerConnected + : screensSelectServer; String translatedName(String key) { switch (key) { @@ -93,8 +93,7 @@ class _LayoutState extends State { (s) => DrawerTile( icon: s.value.icon, title: translatedName(s.value.name), - isSelected: - widget.navigationShell.currentIndex == s.key, + isSelected: appConfigProvider.selectedScreen == s.key, onSelect: () => _goBranch(s.key), withoutTitle: !_drawerExpanded, ), @@ -104,8 +103,7 @@ class _LayoutState extends State { (s) => DrawerTile( icon: s.value.icon, title: translatedName(s.value.name), - isSelected: - widget.navigationShell.currentIndex == s.key, + isSelected: appConfigProvider.selectedScreen == s.key, onSelect: () => _goBranch(s.key), withoutTitle: !_drawerExpanded, ), @@ -114,7 +112,17 @@ class _LayoutState extends State { ), ), Expanded( - child: widget.navigationShell + child: PageTransitionSwitcher( + duration: const Duration(milliseconds: 200), + transitionBuilder: ( + (child, primaryAnimation, secondaryAnimation) => FadeThroughTransition( + animation: primaryAnimation, + secondaryAnimation: secondaryAnimation, + child: child, + ) + ), + child: screens[appConfigProvider.selectedScreen].child, + ), ), ], ), @@ -126,18 +134,28 @@ class _LayoutState extends State { : screensSelectServer; return Scaffold( - body: widget.navigationShell, + body: PageTransitionSwitcher( + duration: const Duration(milliseconds: 200), + transitionBuilder: ( + (child, primaryAnimation, secondaryAnimation) => FadeThroughTransition( + animation: primaryAnimation, + secondaryAnimation: secondaryAnimation, + child: child, + ) + ), + child: screens[appConfigProvider.selectedScreen].child, + ), bottomNavigationBar: NavigationBar( - selectedIndex: (serversProvider.selectedServer == null || serversProvider.apiClient == null) && widget.navigationShell.currentIndex > 1 + selectedIndex: (serversProvider.selectedServer == null || serversProvider.apiClient == null) && appConfigProvider.selectedScreen > 1 ? 0 - : widget.navigationShell.currentIndex, + : appConfigProvider.selectedScreen, onDestinationSelected: (s) => _goBranch(s), destinations: screens.asMap().entries.map((screen) => NavigationDestination( icon: Stack( children: [ Icon( screen.value.icon, - color: widget.navigationShell.currentIndex == screen.key + color: appConfigProvider.selectedScreen == screen.key ? Theme.of(context).colorScheme.onSecondaryContainer : Theme.of(context).colorScheme.onSurfaceVariant, ), diff --git a/pubspec.lock b/pubspec.lock index a194327..cb2cff6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -318,14 +318,6 @@ packages: description: flutter source: sdk version: "0.0.0" - go_router: - dependency: "direct main" - description: - name: go_router - sha256: e156bc1b2088eb5ece9351bccd48c3e1719a4858eacbd44e59162e98a68205d1 - url: "https://pub.dev" - source: hosted - version: "12.0.1" html: dependency: "direct main" description: @@ -398,14 +390,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" - logging: - dependency: transitive - description: - name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" - url: "https://pub.dev" - source: hosted - version: "1.2.0" markdown: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 06ae67d..778459c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,7 +74,6 @@ dependencies: flutter_dotenv: ^5.0.2 flutter_reorderable_list: ^1.3.1 pie_chart: ^5.3.2 - go_router: ^12.0.1 flutter_hooks: ^0.20.3 dev_dependencies: From 9b4cb09dc204e44970d11c4fdfce0fe2473f4ea7 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 15:53:47 +0100 Subject: [PATCH 048/177] Changed segmented buttons --- lib/screens/filters/add_custom_rule.dart | 40 ++++++++++---------- lib/widgets/add_server/add_server_modal.dart | 35 +++++++++-------- pubspec.lock | 10 ++++- pubspec.yaml | 1 + 4 files changed, 51 insertions(+), 35 deletions(-) diff --git a/lib/screens/filters/add_custom_rule.dart b/lib/screens/filters/add_custom_rule.dart index 4b79fc4..c7e5bcd 100644 --- a/lib/screens/filters/add_custom_rule.dart +++ b/lib/screens/filters/add_custom_rule.dart @@ -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'; @@ -124,25 +125,26 @@ class _AddCustomRuleState extends State { ), ), 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: {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), diff --git a/lib/widgets/add_server/add_server_modal.dart b/lib/widgets/add_server/add_server_modal.dart index c587c7e..511d367 100644 --- a/lib/widgets/add_server/add_server_modal.dart +++ b/lib/widgets/add_server/add_server_modal.dart @@ -2,6 +2,7 @@ import 'package:provider/provider.dart'; import 'package:flutter/material.dart'; +import 'package:segmented_button_slide/segmented_button_slide.dart'; import 'package:uuid/uuid.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -371,21 +372,25 @@ class _AddServerModalState extends State { label: AppLocalizations.of(context)!.connection, padding: const EdgeInsets.all(24), ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: SegmentedButton( - segments: const [ - ButtonSegment( - value: ConnectionType.http, - label: Text("HTTP") - ), - ButtonSegment( - value: ConnectionType.https, - label: Text("HTTPS") - ), - ], - selected: {connectionType}, - onSelectionChanged: (value) => setState(() => connectionType = value.first), + SegmentedButtonSlide( + entries: const [ + SegmentedButtonSlideEntry(label: "HTTP"), + SegmentedButtonSlideEntry(label: "HTTPS"), + ], + selectedEntry: connectionType.index, + onChange: (v) => setState(() => connectionType = ConnectionType.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, ), ), const SizedBox(height: 30), diff --git a/pubspec.lock b/pubspec.lock index cb2cff6..0741c5f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -518,6 +518,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.5" + segmented_button_slide: + dependency: "direct main" + description: + name: segmented_button_slide + sha256: "96d67344fa65f6b98a317d930bfd4e81e47c4f9e3e2ab1ee8925b55f72cded13" + url: "https://pub.dev" + source: hosted + version: "1.0.4" sentry: dependency: transitive description: @@ -789,5 +797,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0 <4.0.0" + dart: ">=3.1.3 <4.0.0" flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index 778459c..7fcccf0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -75,6 +75,7 @@ dependencies: flutter_reorderable_list: ^1.3.1 pie_chart: ^5.3.2 flutter_hooks: ^0.20.3 + segmented_button_slide: ^1.0.4 dev_dependencies: flutter_test: From d2c7866f0af813a38638b5a689daac83358a7cb5 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 15:57:47 +0100 Subject: [PATCH 049/177] Fixed overlay style --- lib/main.dart | 100 +++++------ lib/widgets/layout.dart | 214 ++++++++++++----------- lib/widgets/system_ui_overlay_style.dart | 31 ++++ 3 files changed, 183 insertions(+), 162 deletions(-) create mode 100644 lib/widgets/system_ui_overlay_style.dart diff --git a/lib/main.dart b/lib/main.dart index f34459f..b2bb30d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.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'; @@ -230,64 +229,49 @@ class _MainState extends State
with WidgetsBindingObserver { Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); - return AnnotatedRegion( - 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: DynamicColorBuilder( - builder: (lightDynamic, darkDynamic) => MaterialApp( - title: 'AdGuard Home Manager', - theme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 - ? appConfigProvider.useDynamicColor == true - ? lightTheme(lightDynamic) - : lightThemeOldVersions(colors[appConfigProvider.staticColor]) - : lightThemeOldVersions(colors[appConfigProvider.staticColor]), - darkTheme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 - ? appConfigProvider.useDynamicColor == true - ? darkTheme(darkDynamic) - : darkThemeOldVersions(colors[appConfigProvider.staticColor]) - : darkThemeOldVersions(colors[appConfigProvider.staticColor]), - themeMode: appConfigProvider.selectedTheme, - debugShowCheckedModeBanner: false, - localizationsDelegates: const [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - AppLocalizations.delegate, - ], - supportedLocales: const [ - Locale('en', ''), - Locale('es', ''), - Locale('zh', ''), - Locale('zh', 'CN'), - Locale('pl', ''), - Locale('tr', '') - ], - scaffoldMessengerKey: scaffoldMessengerKey, - builder: (context, child) { - return CustomMenuBar( - child: MediaQuery( - data: MediaQuery.of(context).copyWith( - textScaleFactor: !(Platform.isAndroid || Platform.isIOS) - ? 0.9 - : 1.0 - ), - child: child!, + return DynamicColorBuilder( + builder: (lightDynamic, darkDynamic) => MaterialApp( + title: 'AdGuard Home Manager', + theme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 + ? appConfigProvider.useDynamicColor == true + ? lightTheme(lightDynamic) + : lightThemeOldVersions(colors[appConfigProvider.staticColor]) + : lightThemeOldVersions(colors[appConfigProvider.staticColor]), + darkTheme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 + ? appConfigProvider.useDynamicColor == true + ? darkTheme(darkDynamic) + : darkThemeOldVersions(colors[appConfigProvider.staticColor]) + : darkThemeOldVersions(colors[appConfigProvider.staticColor]), + themeMode: appConfigProvider.selectedTheme, + debugShowCheckedModeBanner: false, + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + AppLocalizations.delegate, + ], + supportedLocales: const [ + Locale('en', ''), + Locale('es', ''), + Locale('zh', ''), + Locale('zh', 'CN'), + Locale('pl', ''), + Locale('tr', '') + ], + scaffoldMessengerKey: scaffoldMessengerKey, + builder: (context, child) { + return CustomMenuBar( + child: MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaleFactor: !(Platform.isAndroid || Platform.isIOS) + ? 0.9 + : 1.0 ), - ); - }, - home: Layout(), - ), + child: child!, + ), + ); + }, + home: const Layout(), ), ); } diff --git a/lib/widgets/layout.dart b/lib/widgets/layout.dart index a7da996..8edcd56 100644 --- a/lib/widgets/layout.dart +++ b/lib/widgets/layout.dart @@ -3,6 +3,8 @@ import 'package:animations/animations.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:adguard_home_manager/widgets/system_ui_overlay_style.dart'; + import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/config/app_screens.dart'; import 'package:adguard_home_manager/config/sizes.dart'; @@ -61,70 +63,72 @@ class _LayoutState extends State { } if (width > desktopBreakpoint) { - return Material( - child: Row( - children: [ - AnimatedContainer( - duration: const Duration(milliseconds: 250), - curve: Curves.ease, - width: _drawerExpanded ? 250 : 90, - child: ListView( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 16 + return OverlayStyle( + child: Material( + child: Row( + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.ease, + width: _drawerExpanded ? 250 : 90, + child: ListView( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 16 + ), + child: IconButton( + onPressed: () => setState(() => _drawerExpanded = !_drawerExpanded), + icon: const Icon(Icons.menu_open_rounded), + tooltip: _drawerExpanded == true + ? AppLocalizations.of(context)!.closeMenu + : AppLocalizations.of(context)!.openMenu, + ), ), - child: IconButton( - onPressed: () => setState(() => _drawerExpanded = !_drawerExpanded), - icon: const Icon(Icons.menu_open_rounded), - tooltip: _drawerExpanded == true - ? AppLocalizations.of(context)!.closeMenu - : AppLocalizations.of(context)!.openMenu, + ], + ), + if (serversProvider.selectedServer != null) + ...screensServerConnected.asMap().entries.map( + (s) => DrawerTile( + icon: s.value.icon, + title: translatedName(s.value.name), + isSelected: appConfigProvider.selectedScreen == s.key, + onSelect: () => _goBranch(s.key), + withoutTitle: !_drawerExpanded, ), ), - ], - ), - if (serversProvider.selectedServer != null) - ...screensServerConnected.asMap().entries.map( - (s) => DrawerTile( - icon: s.value.icon, - title: translatedName(s.value.name), - isSelected: appConfigProvider.selectedScreen == s.key, - onSelect: () => _goBranch(s.key), - withoutTitle: !_drawerExpanded, + if (serversProvider.selectedServer == null) + ...screensSelectServer.asMap().entries.map( + (s) => DrawerTile( + icon: s.value.icon, + title: translatedName(s.value.name), + isSelected: appConfigProvider.selectedScreen == s.key, + onSelect: () => _goBranch(s.key), + withoutTitle: !_drawerExpanded, + ), ), - ), - if (serversProvider.selectedServer == null) - ...screensSelectServer.asMap().entries.map( - (s) => DrawerTile( - icon: s.value.icon, - title: translatedName(s.value.name), - isSelected: appConfigProvider.selectedScreen == s.key, - onSelect: () => _goBranch(s.key), - withoutTitle: !_drawerExpanded, - ), - ), - ], - ), - ), - Expanded( - child: PageTransitionSwitcher( - duration: const Duration(milliseconds: 200), - transitionBuilder: ( - (child, primaryAnimation, secondaryAnimation) => FadeThroughTransition( - animation: primaryAnimation, - secondaryAnimation: secondaryAnimation, - child: child, - ) + ], ), - child: screens[appConfigProvider.selectedScreen].child, ), - ), - ], + Expanded( + child: PageTransitionSwitcher( + duration: const Duration(milliseconds: 200), + transitionBuilder: ( + (child, primaryAnimation, secondaryAnimation) => FadeThroughTransition( + animation: primaryAnimation, + secondaryAnimation: secondaryAnimation, + child: child, + ) + ), + child: screens[appConfigProvider.selectedScreen].child, + ), + ), + ], + ), ), ); } @@ -133,54 +137,56 @@ class _LayoutState extends State { ? screensServerConnected : screensSelectServer; - return Scaffold( - body: PageTransitionSwitcher( - duration: const Duration(milliseconds: 200), - transitionBuilder: ( - (child, primaryAnimation, secondaryAnimation) => FadeThroughTransition( - animation: primaryAnimation, - secondaryAnimation: secondaryAnimation, - child: child, - ) + return OverlayStyle( + child: Scaffold( + body: PageTransitionSwitcher( + duration: const Duration(milliseconds: 200), + transitionBuilder: ( + (child, primaryAnimation, secondaryAnimation) => FadeThroughTransition( + animation: primaryAnimation, + secondaryAnimation: secondaryAnimation, + child: child, + ) + ), + child: screens[appConfigProvider.selectedScreen].child, ), - child: screens[appConfigProvider.selectedScreen].child, - ), - bottomNavigationBar: NavigationBar( - selectedIndex: (serversProvider.selectedServer == null || serversProvider.apiClient == null) && appConfigProvider.selectedScreen > 1 - ? 0 - : appConfigProvider.selectedScreen, - onDestinationSelected: (s) => _goBranch(s), - destinations: screens.asMap().entries.map((screen) => NavigationDestination( - icon: Stack( - children: [ - Icon( - screen.value.icon, - color: appConfigProvider.selectedScreen == screen.key - ? Theme.of(context).colorScheme.onSecondaryContainer - : Theme.of(context).colorScheme.onSurfaceVariant, - ), - if ( - screen.value.name == 'settings' && - serversProvider.updateAvailable.data != null && - serversProvider.updateAvailable.data!.canAutoupdate == true - ) Positioned( - bottom: 0, - right: -12, - child: Container( - width: 10, - height: 10, - margin: const EdgeInsets.only(right: 12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.red + bottomNavigationBar: NavigationBar( + selectedIndex: (serversProvider.selectedServer == null || serversProvider.apiClient == null) && appConfigProvider.selectedScreen > 1 + ? 0 + : appConfigProvider.selectedScreen, + onDestinationSelected: (s) => _goBranch(s), + destinations: screens.asMap().entries.map((screen) => NavigationDestination( + icon: Stack( + children: [ + Icon( + screen.value.icon, + color: appConfigProvider.selectedScreen == screen.key + ? Theme.of(context).colorScheme.onSecondaryContainer + : Theme.of(context).colorScheme.onSurfaceVariant, + ), + if ( + screen.value.name == 'settings' && + serversProvider.updateAvailable.data != null && + serversProvider.updateAvailable.data!.canAutoupdate == true + ) Positioned( + bottom: 0, + right: -12, + child: Container( + width: 10, + height: 10, + margin: const EdgeInsets.only(right: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.red + ), ), - ), - ) - ], - ), - label: translatedName(screen.value.name) - )).toList(), - ) + ) + ], + ), + label: translatedName(screen.value.name) + )).toList(), + ) + ), ); } } diff --git a/lib/widgets/system_ui_overlay_style.dart b/lib/widgets/system_ui_overlay_style.dart new file mode 100644 index 0000000..8ceaf4b --- /dev/null +++ b/lib/widgets/system_ui_overlay_style.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class OverlayStyle extends StatelessWidget { + final Widget child; + + const OverlayStyle({ + Key? key, + required this.child + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + 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: child, + ); + } +} \ No newline at end of file From 4d22d8a0a9ae230c122b82ab7afab09f97d526f7 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 19:27:01 +0100 Subject: [PATCH 050/177] Added sign macos app actions --- .github/workflows/release-stable.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/release-stable.yaml b/.github/workflows/release-stable.yaml index cfebecd..42ff984 100644 --- a/.github/workflows/release-stable.yaml +++ b/.github/workflows/release-stable.yaml @@ -75,6 +75,20 @@ jobs: - run: flutter clean - run: flutter pub get - run: flutter build macos --release + - name: Install the Apple certificate and sign the application + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PWD: ${{ secrets.APPLE_CERTIFICATE_PWD }} + APPLE_KEYCHAIN_PWD: ${{ secrets.APPLE_KEYCHAIN_PWD }} + APPLE_IDENTITY_ID: ${{ secrets.APPLE_IDENTITY_ID }} + run: | + echo $APPLE_CERTIFICATE | base64 —decode > certificate.p12 + security create-keychain -p $APPLE_KEYCHAIN_PWD build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p $APPLE_KEYCHAIN_PWD build.keychain + security import certificate.p12 -k build.keychain -P $APPLE_CERTIFICATE_PWD -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $APPLE_KEYCHAIN_PWD build.keychain + /usr/bin/codesign --force -s "$APPLE_IDENTITY_ID" $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager.app -v - name: Create folder to build dmg run: mkdir $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager - name: Copy app into folder From eca69a79903b4abf694ecb90be3ce90ff59799a1 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 19:36:33 +0100 Subject: [PATCH 051/177] Fix job --- .github/workflows/release-stable.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-stable.yaml b/.github/workflows/release-stable.yaml index 42ff984..e61eb21 100644 --- a/.github/workflows/release-stable.yaml +++ b/.github/workflows/release-stable.yaml @@ -82,7 +82,7 @@ jobs: APPLE_KEYCHAIN_PWD: ${{ secrets.APPLE_KEYCHAIN_PWD }} APPLE_IDENTITY_ID: ${{ secrets.APPLE_IDENTITY_ID }} run: | - echo $APPLE_CERTIFICATE | base64 —decode > certificate.p12 + echo $APPLE_CERTIFICATE | base64 -—decode > certificate.p12 security create-keychain -p $APPLE_KEYCHAIN_PWD build.keychain security default-keychain -s build.keychain security unlock-keychain -p $APPLE_KEYCHAIN_PWD build.keychain From 557a9242e780fa7770d7e84e243a822123653d9d Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 19:48:35 +0100 Subject: [PATCH 052/177] Fix job --- .github/workflows/release-stable.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-stable.yaml b/.github/workflows/release-stable.yaml index e61eb21..e8b6de0 100644 --- a/.github/workflows/release-stable.yaml +++ b/.github/workflows/release-stable.yaml @@ -82,7 +82,7 @@ jobs: APPLE_KEYCHAIN_PWD: ${{ secrets.APPLE_KEYCHAIN_PWD }} APPLE_IDENTITY_ID: ${{ secrets.APPLE_IDENTITY_ID }} run: | - echo $APPLE_CERTIFICATE | base64 -—decode > certificate.p12 + echo "$APPLE_CERTIFICATE" | base64 -—decode > certificate.p12 security create-keychain -p $APPLE_KEYCHAIN_PWD build.keychain security default-keychain -s build.keychain security unlock-keychain -p $APPLE_KEYCHAIN_PWD build.keychain From f7260e5ffbf73997d99e3eb141ed01dd453d6ff4 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 20:05:37 +0100 Subject: [PATCH 053/177] Fix job --- .github/workflows/release-stable.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-stable.yaml b/.github/workflows/release-stable.yaml index e8b6de0..b8d2b73 100644 --- a/.github/workflows/release-stable.yaml +++ b/.github/workflows/release-stable.yaml @@ -82,7 +82,7 @@ jobs: APPLE_KEYCHAIN_PWD: ${{ secrets.APPLE_KEYCHAIN_PWD }} APPLE_IDENTITY_ID: ${{ secrets.APPLE_IDENTITY_ID }} run: | - echo "$APPLE_CERTIFICATE" | base64 -—decode > certificate.p12 + echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12 security create-keychain -p $APPLE_KEYCHAIN_PWD build.keychain security default-keychain -s build.keychain security unlock-keychain -p $APPLE_KEYCHAIN_PWD build.keychain From 05e5255467960827d0c2bedf0cfb1f2e1a6ddfe5 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 1 Nov 2023 15:43:19 +0100 Subject: [PATCH 054/177] Refactor client form code --- lib/screens/clients/client/client_form.dart | 265 +++++++++++++ lib/screens/clients/client/client_screen.dart | 353 ++++++------------ .../client/client_screen_functions.dart | 4 +- .../clients/client/identifiers_section.dart | 25 +- .../client/upstream_servers_section.dart | 21 +- 5 files changed, 401 insertions(+), 267 deletions(-) create mode 100644 lib/screens/clients/client/client_form.dart diff --git a/lib/screens/clients/client/client_form.dart b/lib/screens/clients/client/client_form.dart new file mode 100644 index 0000000..ff4db9a --- /dev/null +++ b/lib/screens/clients/client/client_form.dart @@ -0,0 +1,265 @@ +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/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_list_tile.dart'; +import 'package:adguard_home_manager/widgets/section_label.dart'; + +import 'package:adguard_home_manager/functions/compare_versions.dart'; +import 'package:adguard_home_manager/models/clients.dart'; +import 'package:adguard_home_manager/models/safe_search.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; + +class ClientForm extends StatelessWidget { + final bool isFullScreen; + final Client? client; + final TextEditingController nameController; + final void Function(bool) updateValidValues; + final List identifiersControllers; + final List 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 blockedServices; + final void Function(List) updateBlockedServices; + final List upstreamServers; + final void Function(List) updateUpstreamServers; + final void Function(List) updateSelectedTags; + final void Function(List) 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; + + const ClientForm({ + Key? key, + required this.isFullScreen, + required this.client, + required this.nameController, + required this.updateValidValues, + 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, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final statusProvider = Provider.of(context); + + return ListView( + padding: const EdgeInsets.only(top: 0), + children: [ + if (isFullScreen == true) const SizedBox(height: 24), + if (isFullScreen == false) const SizedBox(height: 6), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextFormField( + enabled: client != null ? false : true, + controller: nameController, + onChanged: (_) => updateValidValues( + checkValidValues( + identifiersControllers: identifiersControllers, + nameController: nameController + ) + ), + 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.all(24), + ), + TagsSection( + selectedTags: selectedTags, + onTagsSelected: updateSelectedTags + ), + IdentifiersSection( + identifiersControllers: identifiersControllers, + onUpdateIdentifiersControllers: (c) { + updateIdentifiersControllers(c); + updateValidValues( + checkValidValues( + nameController: nameController, + identifiersControllers: identifiersControllers + ) + ); + }, + onCheckValidValues: () => updateValidValues( + checkValidValues( + identifiersControllers: identifiersControllers, + nameController: nameController + ) + ), + ), + 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: enableDisableGlobalSettingsFiltering, + 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: (value) => enableDisableGlobalSettingsFiltering() + ) + ], + ), + ), + ), + ), + ), + const SizedBox(height: 10), + 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, + ), + 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( + context: context, + blockedServices: blockedServices, + defaultSafeSearch: defaultSafeSearch, + safeSearch: safeSearch, + onUpdateSafeSearch: updateSafeSearch + ) + : null, + ), + if ( + serverVersionIsAhead( + currentVersion: statusProvider.serverStatus!.serverVersion, + referenceVersion: 'v0.107.28', + referenceVersionBeta: 'v0.108.0-b.33' + ) == false + ) SettingsTile( + label: AppLocalizations.of(context)!.enableSafeSearch, + value: enableSafeSearch, + onChange: (value) => updateEnableSafeSearch(value), + useGlobalSettingsFiltering: useGlobalSettingsFiltering, + ), + SectionLabel( + label: AppLocalizations.of(context)!.blockedServices, + padding: const EdgeInsets.all(24), + ), + BlockedServicesSection( + useGlobalSettingsServices: useGlobalSettingsServices, + blockedServices: blockedServices, + onUpdatedBlockedServices: updateBlockedServices, + onUpdateServicesGlobalSettings: updateUseGlobalSettingsServices, + ), + UpstreamServersSection( + upstreamServers: upstreamServers, + onCheckValidValues: () => updateValidValues( + checkValidValues( + identifiersControllers: identifiersControllers, + nameController: nameController + ) + ), + onUpdateUpstreamServers: updateUpstreamServers + ), + SizedBox(height: Platform.isIOS ? 48 : 24) + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/client/client_screen.dart b/lib/screens/clients/client/client_screen.dart index 4fcee53..243981c 100644 --- a/lib/screens/clients/client/client_screen.dart +++ b/lib/screens/clients/client/client_screen.dart @@ -3,14 +3,8 @@ 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/upstream_servers_section.dart'; -import 'package:adguard_home_manager/screens/clients/client/identifiers_section.dart'; -import 'package:adguard_home_manager/screens/clients/client/blocked_services_section.dart'; -import 'package:adguard_home_manager/screens/clients/client/tags_section.dart'; -import 'package:adguard_home_manager/screens/clients/client/settings_tile.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/widgets/section_label.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'; @@ -18,6 +12,16 @@ 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 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; @@ -45,11 +49,8 @@ class _ClientScreenState extends State { List selectedTags = []; - List> identifiersControllers = [ - { - 'id': 0, - 'controller': TextEditingController() - } + List identifiersControllers = [ + ControllerListItem(id: "0", controller: TextEditingController()) ]; bool useGlobalSettingsFiltering = true; @@ -72,10 +73,35 @@ class _ClientScreenState extends State { bool useGlobalSettingsServices = true; List blockedServices = []; - List> upstreamServers = []; + List upstreamServers = []; bool version = false; + 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() { version = serverVersionIsAhead( @@ -89,10 +115,10 @@ class _ClientScreenState extends State { nameController.text = widget.client!.name; selectedTags = widget.client!.tags; - identifiersControllers = widget.client!.ids.map((e) => { - 'id': uuid.v4(), - 'controller': TextEditingController(text: e) - }).toList(); + 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; @@ -105,10 +131,10 @@ class _ClientScreenState extends State { } useGlobalSettingsServices = widget.client!.useGlobalBlockedServices; blockedServices = widget.client!.blockedServices; - upstreamServers = widget.client!.upstreams.map((e) => { - 'id': uuid.v4(), - 'controller': TextEditingController(text: e) - }).toList(); + upstreamServers = widget.client!.upstreams.map((e) => ControllerListItem( + id: uuid.v4(), + controller: TextEditingController(text: e) + )).toList(); } super.initState(); } @@ -116,12 +142,11 @@ class _ClientScreenState extends State { @override Widget build(BuildContext context) { final clientsProvider = Provider.of(context); - final statusProvider = Provider.of(context); void createClient() { final Client client = Client( name: nameController.text, - ids: List.from(identifiersControllers.map((e) => e['controller'].text)), + ids: List.from(identifiersControllers.map((e) => e.controller.text)), useGlobalSettings: useGlobalSettingsFiltering, filteringEnabled: enableFiltering ?? false, parentalEnabled: enableParentalControl ?? false, @@ -130,51 +155,12 @@ class _ClientScreenState extends State { safeSearch: version == true ? safeSearch : null, useGlobalBlockedServices: useGlobalSettingsServices, blockedServices: blockedServices, - upstreams: List.from(upstreamServers.map((e) => e['controller'].text)), + upstreams: List.from(upstreamServers.map((e) => e.controller.text)), tags: selectedTags ); widget.onConfirm(client); } - 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 updateServicesGlobalSettings(bool value) { - if (value == true) { - setState(() { - blockedServices = []; - useGlobalSettingsServices = true; - }); - } - else if (value == false) { - setState(() { - useGlobalSettingsServices = false; - }); - } - } - List actions() { return [ IconButton( @@ -199,184 +185,6 @@ class _ClientScreenState extends State { ]; } - 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: (_) => setState(() { - validValues = checkValidValues( - identifiersControllers: identifiersControllers, - nameController: nameController - ); - }), - 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.all(24), - ), - TagsSection( - selectedTags: selectedTags, - onTagsSelected: (tags) => setState(() => selectedTags = tags) - ), - IdentifiersSection( - identifiersControllers: identifiersControllers, - onUpdateIdentifiersControllers: (c) => setState(() { - identifiersControllers = c; - validValues = checkValidValues( - nameController: nameController, - identifiersControllers: identifiersControllers - ); - }), - onCheckValidValues: () => setState(() { - validValues = checkValidValues( - identifiersControllers: identifiersControllers, - nameController: nameController - ); - }), - ), - 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: () => enableDisableGlobalSettingsFiltering(), - 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: (value) => enableDisableGlobalSettingsFiltering() - ) - ], - ), - ), - ), - ), - ), - const SizedBox(height: 10), - SettingsTile( - label: AppLocalizations.of(context)!.enableFiltering, - value: enableFiltering, - onChange: (value) => setState(() => enableFiltering = value), - useGlobalSettingsFiltering: useGlobalSettingsFiltering, - ), - SettingsTile( - label: AppLocalizations.of(context)!.enableSafeBrowsing, - value: enableSafeBrowsing, - onChange: (value) => setState(() => enableSafeBrowsing = value), - useGlobalSettingsFiltering: useGlobalSettingsFiltering, - ), - SettingsTile( - label: AppLocalizations.of(context)!.enableParentalControl, - value: enableParentalControl, - onChange: (value) => setState(() => enableParentalControl = value), - useGlobalSettingsFiltering: useGlobalSettingsFiltering, - ), - 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( - context: context, - blockedServices: blockedServices, - defaultSafeSearch: defaultSafeSearch, - safeSearch: safeSearch, - onUpdateSafeSearch: (s) => setState(() => safeSearch = s) - ) - : null, - ), - if ( - serverVersionIsAhead( - currentVersion: statusProvider.serverStatus!.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == false - ) SettingsTile( - label: AppLocalizations.of(context)!.enableSafeSearch, - value: enableSafeSearch, - onChange: (value) => setState(() => enableSafeSearch = value), - useGlobalSettingsFiltering: useGlobalSettingsFiltering, - ), - SectionLabel( - label: AppLocalizations.of(context)!.blockedServices, - padding: const EdgeInsets.all(24), - ), - BlockedServicesSection( - useGlobalSettingsServices: useGlobalSettingsServices, - blockedServices: blockedServices, - onUpdatedBlockedServices: (s) => setState(() => blockedServices = s), - onUpdateServicesGlobalSettings: (v) => setState(() => useGlobalSettingsServices = v), - ), - UpstreamServersSection( - upstreamServers: upstreamServers, - onCheckValidValues: () => setState(() { - validValues = checkValidValues( - identifiersControllers: identifiersControllers, - nameController: nameController - ); - }), - onUpdateUpstreamServers: (v) => setState(() => upstreamServers = v) - ), - const SizedBox(height: 20) - ], - ); - } - if (widget.fullScreen == true) { return Dialog.fullscreen( @@ -393,7 +201,35 @@ class _ClientScreenState extends State { ), actions: actions(), ), - body: content(true) + body: ClientForm( + isFullScreen: true, + client: widget.client, + nameController: nameController, + updateValidValues: (v) => setState(() => validValues = v), + 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), + ), ), ); } @@ -431,7 +267,35 @@ class _ClientScreenState extends State { ), ), Flexible( - child: content(false) + child: ClientForm( + isFullScreen: false, + client: widget.client, + nameController: nameController, + updateValidValues: (v) => setState(() => validValues = v), + 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), + ), ) ], ), @@ -439,4 +303,5 @@ class _ClientScreenState extends State { ); } } -} \ No newline at end of file +} + diff --git a/lib/screens/clients/client/client_screen_functions.dart b/lib/screens/clients/client/client_screen_functions.dart index 4b84b2b..1f292c6 100644 --- a/lib/screens/clients/client/client_screen_functions.dart +++ b/lib/screens/clients/client/client_screen_functions.dart @@ -76,12 +76,12 @@ void openSafeSearchModal({ bool checkValidValues({ required TextEditingController nameController, - required List> identifiersControllers + required List identifiersControllers }) { if ( nameController.text != '' && identifiersControllers.isNotEmpty && - identifiersControllers[0]['controller']!.text != '' + identifiersControllers[0].controller.text != '' ) { return true; } diff --git a/lib/screens/clients/client/identifiers_section.dart b/lib/screens/clients/client/identifiers_section.dart index 3d9acf2..91a56eb 100644 --- a/lib/screens/clients/client/identifiers_section.dart +++ b/lib/screens/clients/client/identifiers_section.dart @@ -1,3 +1,4 @@ +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'; @@ -5,8 +6,8 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; class IdentifiersSection extends StatefulWidget { - final List> identifiersControllers; - final void Function(List>) onUpdateIdentifiersControllers; + final List identifiersControllers; + final void Function(List) onUpdateIdentifiersControllers; final void Function() onCheckValidValues; const IdentifiersSection({ @@ -41,10 +42,10 @@ class _IdentifiersSectionState extends State { child: IconButton( onPressed: () => widget.onUpdateIdentifiersControllers([ ...widget.identifiersControllers, - Map.from({ - 'id': uuid.v4(), - 'controller': TextEditingController() - }) + ControllerListItem( + id: uuid.v4(), + controller: TextEditingController() + ), ]), icon: const Icon(Icons.add) ), @@ -52,13 +53,15 @@ class _IdentifiersSectionState extends State { ], ), if (widget.identifiersControllers.isNotEmpty) ...widget.identifiersControllers.map((controller) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + padding: const EdgeInsets.only( + top: 12, bottom: 12, left: 24, right: 20 + ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: TextFormField( - controller: controller['controller'], + controller: controller.controller, onChanged: (_) => widget.onCheckValidValues(), decoration: InputDecoration( prefixIcon: const Icon(Icons.tag), @@ -72,12 +75,12 @@ class _IdentifiersSectionState extends State { ), ), ), - const SizedBox(width: 20), + const SizedBox(width: 16), Padding( padding: const EdgeInsets.only(bottom: 25), child: IconButton( onPressed: () => widget.onUpdateIdentifiersControllers( - widget.identifiersControllers.where((e) => e['id'] != controller['id']).toList() + widget.identifiersControllers.where((e) => e.id != controller.id).toList() ), icon: const Icon(Icons.remove_circle_outline_outlined) ), @@ -86,7 +89,7 @@ class _IdentifiersSectionState extends State { ), )).toList(), if (widget.identifiersControllers.isEmpty) Container( - padding: const EdgeInsets.only(top: 10), + padding: const EdgeInsets.symmetric(vertical: 16), child: Text( AppLocalizations.of(context)!.noIdentifiers, textAlign: TextAlign.center, diff --git a/lib/screens/clients/client/upstream_servers_section.dart b/lib/screens/clients/client/upstream_servers_section.dart index 47dd41d..97c8974 100644 --- a/lib/screens/clients/client/upstream_servers_section.dart +++ b/lib/screens/clients/client/upstream_servers_section.dart @@ -1,3 +1,4 @@ +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'; @@ -5,9 +6,9 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; class UpstreamServersSection extends StatefulWidget { - final List> upstreamServers; + final List upstreamServers; final void Function() onCheckValidValues; - final void Function(List>) onUpdateUpstreamServers; + final void Function(List) onUpdateUpstreamServers; const UpstreamServersSection({ Key? key, @@ -38,10 +39,10 @@ class _UpstreamServersSectionState extends State { padding: const EdgeInsets.only(right: 20), child: IconButton( onPressed: () => setState(() => widget.upstreamServers.add( - Map.from({ - 'id': uuid.v4(), - 'controller': TextEditingController() - }) + ControllerListItem( + id: uuid.v4(), + controller: TextEditingController() + ) )), icon: const Icon(Icons.add) ), @@ -57,7 +58,7 @@ class _UpstreamServersSectionState extends State { children: [ Expanded( child: TextFormField( - controller: controller['controller'], + controller: controller.controller, onChanged: (_) => widget.onCheckValidValues, decoration: InputDecoration( prefixIcon: const Icon(Icons.dns_rounded), @@ -70,10 +71,10 @@ class _UpstreamServersSectionState extends State { ), ), ), - const SizedBox(width: 20), + const SizedBox(width: 16), IconButton( onPressed: () => widget.onUpdateUpstreamServers( - widget.upstreamServers.where((e) => e['id'] != controller['id']).toList() + widget.upstreamServers.where((e) => e.id != controller.id).toList() ), icon: const Icon(Icons.remove_circle_outline_outlined) ) @@ -82,7 +83,7 @@ class _UpstreamServersSectionState extends State { ), )).toList(), if (widget.upstreamServers.isEmpty) Container( - padding: const EdgeInsets.only(top: 12), + padding: const EdgeInsets.symmetric(vertical: 16), child: Column( children: [ Text( From 9bca87a4f24b3c7f9e70039eb43f9820b1949b42 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 1 Nov 2023 15:43:33 +0100 Subject: [PATCH 055/177] Updated iOS stuff --- ios/Podfile.lock | 52 +++++++++++++++++-- ios/Runner.xcodeproj/project.pbxproj | 3 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9a238fd..ed1a57c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -11,11 +11,35 @@ PODS: - FMDB/standard (2.7.5) - package_info_plus (0.4.5): - Flutter - - sqflite (0.0.2): + - Sentry/HybridSDK (8.9.1): + - SentryPrivate (= 8.9.1) + - sentry_flutter (0.0.1): + - Flutter + - FlutterMacOS + - Sentry/HybridSDK (= 8.9.1) + - SentryPrivate (8.9.1) + - sqflite (0.0.3): - Flutter - FMDB (>= 2.7.5) + - sqlite3 (3.43.1): + - sqlite3/common (= 3.43.1) + - sqlite3/common (3.43.1) + - sqlite3/fts5 (3.43.1): + - sqlite3/common + - sqlite3/perf-threadsafe (3.43.1): + - sqlite3/common + - sqlite3/rtree (3.43.1): + - sqlite3/common + - sqlite3_flutter_libs (0.0.1): + - Flutter + - sqlite3 (~> 3.43.0) + - sqlite3/fts5 + - sqlite3/perf-threadsafe + - sqlite3/rtree - store_checker (0.0.1): - Flutter + - url_launcher_ios (0.0.1): + - Flutter DEPENDENCIES: - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) @@ -23,12 +47,18 @@ DEPENDENCIES: - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_web_browser (from `.symlinks/plugins/flutter_web_browser/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) + - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) + - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`) - store_checker (from `.symlinks/plugins/store_checker/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) SPEC REPOS: trunk: - FMDB + - Sentry + - SentryPrivate + - sqlite3 EXTERNAL SOURCES: device_info_plus: @@ -41,21 +71,33 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_web_browser/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" + sentry_flutter: + :path: ".symlinks/plugins/sentry_flutter/ios" sqflite: :path: ".symlinks/plugins/sqflite/ios" + sqlite3_flutter_libs: + :path: ".symlinks/plugins/sqlite3_flutter_libs/ios" store_checker: :path: ".symlinks/plugins/store_checker/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed + device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef flutter_web_browser: 7bccaafbb0c5b8862afe7bcd158f15557109f61f FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 + package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 + Sentry: e3203780941722a1fcfee99e351de14244c7f806 + sentry_flutter: 8f0ffd53088e6a4d50c095852c5cad9e4405025c + SentryPrivate: 5e3683390f66611fc7c6215e27645873adb55d13 + sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a + sqlite3: e0a0623a33a20a47cb5921552aebc6e9e437dc91 + sqlite3_flutter_libs: 878ccbdcfd7b7cb41a774ec238223d876880c5ec store_checker: 359c5051d9ec30ff0a8fa39eb5ec9df021bb745d + url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 -COCOAPODS: 1.11.2 +COCOAPODS: 1.12.1 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 7d212ec..4f766b9 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -156,7 +156,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -222,6 +222,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a..a6b826d 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Date: Wed, 1 Nov 2023 18:52:30 +0100 Subject: [PATCH 056/177] Refactor code --- lib/screens/logs/clients_modal.dart | 315 ++++++++++-------- lib/screens/logs/logs.dart | 2 - lib/screens/logs/logs_filters_modal.dart | 403 +++++++++++------------ lib/screens/logs/logs_list.dart | 293 +--------------- lib/screens/logs/logs_list_appbar.dart | 317 ++++++++++++++++++ 5 files changed, 693 insertions(+), 637 deletions(-) create mode 100644 lib/screens/logs/logs_list_appbar.dart diff --git a/lib/screens/logs/clients_modal.dart b/lib/screens/logs/clients_modal.dart index fcd9d74..7bb0574 100644 --- a/lib/screens/logs/clients_modal.dart +++ b/lib/screens/logs/clients_modal.dart @@ -32,156 +32,18 @@ class _ClientsModalState extends State { @override Widget build(BuildContext context) { - final logsProvider = Provider.of(context); - final clientsProvider = Provider.of(context); - final height = MediaQuery.of(context).size.height; - void apply() async { - logsProvider.setSelectedClients( - selectedClients.isNotEmpty ? selectedClients : null - ); - - Navigator.pop(context); - } - - Widget listItem({ - required String label, - required void Function() onChanged - }) { - return Material( - color: Colors.transparent, - child: InkWell( - onTap: () => onChanged(), - child: Padding( - padding: const EdgeInsets.only( - left: 24, - top: 8, - right: 12, - bottom: 8 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Text( - label, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - ), - Checkbox( - value: selectedClients.contains(label), - onChanged: (_) => onChanged(), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5) - ), - ) - ], - ), - ), - ), - ); - } - - void selectAll() { - setState(() { - selectedClients = clientsProvider.clients!.autoClients.map((item) => item.ip).toList(); - }); - } - - void unselectAll() { - setState(() { - selectedClients = []; - }); - } - - Widget content() { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Column( - children: [ - Padding( - padding: const EdgeInsets.only( - top: 24, - bottom: 16, - ), - child: Icon( - Icons.smartphone_rounded, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - Text( - AppLocalizations.of(context)!.clients, - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurface - ), - ), - const SizedBox(height: 16), - ], - ), - Flexible( - child: ListView.builder( - itemCount: clientsProvider.clients!.autoClients.length, - itemBuilder: (context, index) => listItem( - label: clientsProvider.clients!.autoClients[index].ip, - onChanged: () { - if (selectedClients.contains(clientsProvider.clients!.autoClients[index].ip)) { - setState(() { - selectedClients = selectedClients.where( - (item) => item != clientsProvider.clients!.autoClients[index].ip - ).toList(); - }); - } - else { - setState(() { - selectedClients.add(clientsProvider.clients!.autoClients[index].ip); - }); - } - } - ) - ) - ), - Padding( - padding: const EdgeInsets.all(24), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: selectedClients.length == clientsProvider.clients!.autoClients.length - ? () => unselectAll() - : () => selectAll(), - child: Text( - selectedClients.length == clientsProvider.clients!.autoClients.length - ? AppLocalizations.of(context)!.unselectAll - : AppLocalizations.of(context)!.selectAll - ) - ), - TextButton( - onPressed: apply, - child: Text(AppLocalizations.of(context)!.apply) - ) - ], - ), - ), - if (Platform.isIOS) const SizedBox(height: 16) - ], - ); - } - if (widget.dialog == true) { return Dialog( child: ConstrainedBox( constraints: const BoxConstraints( maxWidth: 500 ), - child: content() + child: _ModalContent( + selectedClients: selectedClients, + onClientsSelected: (v) => setState(() => selectedClients = v), + ) ), ); } @@ -198,9 +60,176 @@ class _ClientsModalState extends State { ), color: Theme.of(context).dialogBackgroundColor ), - child: content() + child: _ModalContent( + selectedClients: selectedClients, + onClientsSelected: (v) => setState(() => selectedClients = v), + ) ), ); } } +} + +class _ModalContent extends StatelessWidget { + final List selectedClients; + final void Function(List) onClientsSelected; + + const _ModalContent({ + Key? key, + required this.selectedClients, + required this.onClientsSelected, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final clientsProvider = Provider.of(context); + final logsProvider = Provider.of(context); + + void apply() async { + logsProvider.setSelectedClients( + selectedClients.isNotEmpty ? selectedClients : null + ); + + Navigator.pop(context); + } + + void selectAll() { + onClientsSelected( + clientsProvider.clients!.autoClients.map((item) => item.ip).toList() + ); + } + + void unselectAll() { + onClientsSelected([]); + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + children: [ + Padding( + padding: const EdgeInsets.only( + top: 24, + bottom: 16, + ), + child: Icon( + Icons.smartphone_rounded, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + ), + Text( + AppLocalizations.of(context)!.clients, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurface + ), + ), + const SizedBox(height: 16), + ], + ), + Flexible( + child: ListView.builder( + itemCount: clientsProvider.clients!.autoClients.length, + itemBuilder: (context, index) => _ListItem( + label: clientsProvider.clients!.autoClients[index].ip, + checkboxActive: selectedClients.contains(clientsProvider.clients!.autoClients[index].ip), + onChanged: (isSelected) { + if (isSelected == true) { + onClientsSelected([ + ...selectedClients, + clientsProvider.clients!.autoClients[index].ip + ]); + } + else { + onClientsSelected( + selectedClients.where( + (item) => item != clientsProvider.clients!.autoClients[index].ip + ).toList() + ); + } + } + ) + ) + ), + Padding( + padding: const EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: selectedClients.length == clientsProvider.clients!.autoClients.length + ? () => unselectAll() + : () => selectAll(), + child: Text( + selectedClients.length == clientsProvider.clients!.autoClients.length + ? AppLocalizations.of(context)!.unselectAll + : AppLocalizations.of(context)!.selectAll + ) + ), + TextButton( + onPressed: apply, + child: Text(AppLocalizations.of(context)!.apply) + ) + ], + ), + ), + if (Platform.isIOS) const SizedBox(height: 16) + ], + ); + } +} + +class _ListItem extends StatelessWidget { + final String label; + final bool checkboxActive; + final void Function(bool) onChanged; + + const _ListItem({ + Key? key, + required this.label, + required this.checkboxActive, + required this.onChanged, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () => onChanged(!checkboxActive), + child: Padding( + padding: const EdgeInsets.only( + left: 24, + top: 8, + right: 12, + bottom: 8 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + label, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ), + Checkbox( + value: checkboxActive, + onChanged: (v) => onChanged(!checkboxActive), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5) + ), + ) + ], + ), + ), + ), + ); + } } \ No newline at end of file diff --git a/lib/screens/logs/logs.dart b/lib/screens/logs/logs.dart index b218ad7..863f93d 100644 --- a/lib/screens/logs/logs.dart +++ b/lib/screens/logs/logs.dart @@ -18,8 +18,6 @@ class _LogsState extends State { @override Widget build(BuildContext context) { - - return LayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth > 1000) { diff --git a/lib/screens/logs/logs_filters_modal.dart b/lib/screens/logs/logs_filters_modal.dart index 8f1d7f0..e41efa5 100644 --- a/lib/screens/logs/logs_filters_modal.dart +++ b/lib/screens/logs/logs_filters_modal.dart @@ -15,7 +15,7 @@ import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; -class LogsFiltersModal extends StatelessWidget { +class LogsFiltersModal extends StatefulWidget { final bool dialog; const LogsFiltersModal({ @@ -24,39 +24,67 @@ class LogsFiltersModal extends StatelessWidget { }) : super(key: key); @override - Widget build(BuildContext context) { - final logsProvider = Provider.of(context); - - return LogsFiltersModalWidget( - logsProvider: logsProvider, - dialog: dialog, - ); - } + State createState() => _LogsFiltersModalState(); } -class LogsFiltersModalWidget extends StatefulWidget { - final LogsProvider logsProvider; - final bool dialog; - - const LogsFiltersModalWidget({ - Key? key, - required this.logsProvider, - required this.dialog - }) : super(key: key); - - @override - State createState() => _LogsFiltersModalWidgetState(); -} - -class _LogsFiltersModalWidgetState extends State { +class _LogsFiltersModalState extends State { TextEditingController searchController = TextEditingController(); @override void initState() { - searchController.text = widget.logsProvider.searchText ?? ''; + searchController.text = Provider.of(context, listen: false).searchText ?? ''; super.initState(); } + @override + Widget build(BuildContext context) { + if (widget.dialog == true) { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 500 + ), + child: _FiltersList( + searchController: searchController, + onClearSearch: () => setState(() => searchController.text = "") + ) + ) + ), + ); + } + else { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).dialogBackgroundColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) + ) + ), + child: _FiltersList( + searchController: searchController, + onClearSearch: () => setState(() => searchController.text = "") + ) + ), + ); + } + } +} + +class _FiltersList extends StatelessWidget { + final TextEditingController searchController; + final void Function() onClearSearch; + + const _FiltersList({ + Key? key, + required this.searchController, + required this.onClearSearch, + }) : super(key: key); + @override Widget build(BuildContext context) { final logsProvider = Provider.of(context); @@ -125,200 +153,167 @@ class _LogsFiltersModalWidgetState extends State { } } - 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, - bottom: 16, - ), - child: Icon( - Icons.filter_list_rounded, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - Text( - AppLocalizations.of(context)!.filters, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w400, - height: 1.3, - color: Theme.of(context).colorScheme.onSurface - ), - ), - const SizedBox(height: 16), - ], - ), - ], - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Row( + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SingleChildScrollView( + child: Wrap( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( children: [ - Expanded( - child: TextFormField( - controller: searchController, - onChanged: logsProvider.setSearchText, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.search_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.search, - suffixIcon: IconButton( - onPressed: () { - setState(() { - searchController.text = ''; - }); - logsProvider.setSearchText(null); - }, - icon: const Icon(Icons.clear) - ), - ), + Padding( + padding: const EdgeInsets.only( + top: 24, + bottom: 16, ), - ) + child: Icon( + Icons.filter_list_rounded, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + ), + Text( + AppLocalizations.of(context)!.filters, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w400, + height: 1.3, + color: Theme.of(context).colorScheme.onSurface + ), + ), + const SizedBox(height: 16), ], ), - ), - Container(height: 16), - CustomListTile( - title: AppLocalizations.of(context)!.client, - subtitle: logsProvider.selectedClients != null - ? "${logsProvider.selectedClients!.length} ${AppLocalizations.of(context)!.clientsSelected}" - : AppLocalizations.of(context)!.all, - onTap: clientsProvider.loadStatus == LoadStatus.loaded - ? openSelectClients - : null, - disabled: clientsProvider.loadStatus != LoadStatus.loaded, - icon: Icons.smartphone_rounded, - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - trailing: clientsProvider.loadStatus == LoadStatus.loading - ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + children: [ + Expanded( + child: TextFormField( + controller: searchController, + onChanged: logsProvider.setSearchText, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.search, + suffixIcon: IconButton( + onPressed: () { + onClearSearch(); + logsProvider.setSearchText(null); + }, + icon: const Icon(Icons.clear) + ), + ), ), ) - : clientsProvider.loadStatus == LoadStatus.error - ? const Icon( - Icons.error_rounded, - color: Colors.red, - ) - : null, - ), - SectionLabel( - label: AppLocalizations.of(context)!.quickFilters, - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 16 - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - FilterChip( - selected: logsProvider.selectedResultStatus == "all", - label: Text(AppLocalizations.of(context)!.all), - onSelected: (_) => logsProvider.setSelectedResultStatus("all") - ), - FilterChip( - selected: logsProvider.selectedResultStatus == "processed" || - logsProvider.selectedResultStatus == "whitelisted", - label: Text(AppLocalizations.of(context)!.allowed), - onSelected: (_) => logsProvider.setSelectedResultStatus("processed") - ), - FilterChip( - selected: logsProvider.selectedResultStatus == "blocked" || - logsProvider.selectedResultStatus == "blocked_safebrowsing" || - logsProvider.selectedResultStatus == "blocked_parental" || - logsProvider.selectedResultStatus == "safe_search", - label: Text(AppLocalizations.of(context)!.blocked), - onSelected: (_) => logsProvider.setSelectedResultStatus("blocked") - ), ], ), - const Padding(padding: EdgeInsets.all(8)), - CustomListTile( - title: AppLocalizations.of(context)!.responseStatus, - subtitle: "${translatedString[logsProvider.selectedResultStatus]}", - onTap: openSelectFilterStatus, - icon: Icons.shield_rounded, - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - ), - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.all(24), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: () { - searchController.text = ""; - logsProvider.requestResetFilters(); - }, - child: Text(AppLocalizations.of(context)!.resetFilters) ), - TextButton( - onPressed: () { - Navigator.pop(context); - logsProvider.filterLogs(); - }, - child: Text(AppLocalizations.of(context)!.apply) + Container(height: 16), + CustomListTile( + title: AppLocalizations.of(context)!.client, + subtitle: logsProvider.selectedClients != null + ? "${logsProvider.selectedClients!.length} ${AppLocalizations.of(context)!.clientsSelected}" + : AppLocalizations.of(context)!.all, + onTap: clientsProvider.loadStatus == LoadStatus.loaded + ? openSelectClients + : null, + disabled: clientsProvider.loadStatus != LoadStatus.loaded, + icon: Icons.smartphone_rounded, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + trailing: clientsProvider.loadStatus == LoadStatus.loading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ) + : clientsProvider.loadStatus == LoadStatus.error + ? const Icon( + Icons.error_rounded, + color: Colors.red, + ) + : null, + ), + SectionLabel( + label: AppLocalizations.of(context)!.quickFilters, + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 16 + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + FilterChip( + selected: logsProvider.selectedResultStatus == "all", + label: Text(AppLocalizations.of(context)!.all), + onSelected: (_) => logsProvider.setSelectedResultStatus("all") + ), + FilterChip( + selected: logsProvider.selectedResultStatus == "processed" || + logsProvider.selectedResultStatus == "whitelisted", + label: Text(AppLocalizations.of(context)!.allowed), + onSelected: (_) => logsProvider.setSelectedResultStatus("processed") + ), + FilterChip( + selected: logsProvider.selectedResultStatus == "blocked" || + logsProvider.selectedResultStatus == "blocked_safebrowsing" || + logsProvider.selectedResultStatus == "blocked_parental" || + logsProvider.selectedResultStatus == "safe_search", + label: Text(AppLocalizations.of(context)!.blocked), + onSelected: (_) => logsProvider.setSelectedResultStatus("blocked") + ), + ], + ), + const Padding(padding: EdgeInsets.all(8)), + CustomListTile( + title: AppLocalizations.of(context)!.responseStatus, + subtitle: "${translatedString[logsProvider.selectedResultStatus]}", + onTap: openSelectFilterStatus, + icon: Icons.shield_rounded, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), ), ], ), ), - if (Platform.isIOS) const SizedBox(height: 16) - ], - ); - } - - if (widget.dialog == true) { - return Padding( - padding: MediaQuery.of(context).viewInsets, - child: Dialog( - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 500 - ), - child: content() - ) ), - ); - } - else { - return Padding( - padding: MediaQuery.of(context).viewInsets, - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).dialogBackgroundColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28) - ) + Padding( + padding: const EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () { + searchController.text = ""; + logsProvider.requestResetFilters(); + }, + child: Text(AppLocalizations.of(context)!.resetFilters) + ), + TextButton( + onPressed: () { + Navigator.pop(context); + logsProvider.filterLogs(); + }, + child: Text(AppLocalizations.of(context)!.apply) + ), + ], ), - child: content() ), - ); - } + if (Platform.isIOS) const SizedBox(height: 16) + ], + ); } } \ No newline at end of file diff --git a/lib/screens/logs/logs_list.dart b/lib/screens/logs/logs_list.dart index b04fe74..72978ce 100644 --- a/lib/screens/logs/logs_list.dart +++ b/lib/screens/logs/logs_list.dart @@ -1,27 +1,19 @@ // 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/logs/log_details_screen.dart'; import 'package:adguard_home_manager/screens/logs/log_tile.dart'; -import 'package:adguard_home_manager/screens/logs/logs_config_modal.dart'; -import 'package:adguard_home_manager/screens/logs/logs_filters_modal.dart'; +import 'package:adguard_home_manager/screens/logs/logs_list_appbar.dart'; -import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/functions/compare_versions.dart'; -import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; -import 'package:adguard_home_manager/models/applied_filters.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/clients_provider.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; class LogsListWidget extends StatefulWidget { @@ -99,292 +91,17 @@ class _LogsListWidgetState extends State { @override Widget build(BuildContext context) { - final serversProvider = Provider.of(context); - final statusProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); final logsProvider = Provider.of(context); - - final width = MediaQuery.of(context).size.width; - - void updateConfig(Map data) async { - ProcessModal processModal = ProcessModal(context: context); - processModal.open(AppLocalizations.of(context)!.updatingSettings); - - final result = serverVersionIsAhead( - currentVersion: statusProvider.serverStatus!.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == true - ? await serversProvider.apiClient!.updateQueryLogParameters(data: data) - : await serversProvider.apiClient!.updateQueryLogParametersLegacy(data: data); - - processModal.close(); - - if (result['result'] == 'success') { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsConfigUpdated, - color: Colors.green - ); - } - else { - appConfigProvider.addLog(result['log']); - - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsConfigNotUpdated, - color: Colors.red - ); - } - } - - void clearQueries() async { - ProcessModal processModal = ProcessModal(context: context); - processModal.open(AppLocalizations.of(context)!.updatingSettings); - - final result = await serversProvider.apiClient!.clearLogs(); - - processModal.close(); - - if (result['result'] == 'success') { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsCleared, - color: Colors.green - ); - } - else { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsNotCleared, - color: Colors.red - ); - } - } - - - void openFilersModal() { - if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => const LogsFiltersModal( - dialog: true, - ), - barrierDismissible: false - ); - } - else { - showModalBottomSheet( - context: context, - useRootNavigator: true, - builder: (context) => const LogsFiltersModal( - dialog: false, - ), - backgroundColor: Colors.transparent, - isScrollControlled: true - ); - } - } - - final Map translatedString = { - "all": AppLocalizations.of(context)!.all, - "filtered": AppLocalizations.of(context)!.filtered, - "processed": AppLocalizations.of(context)!.processedRow, - "whitelisted": AppLocalizations.of(context)!.processedWhitelistRow, - "blocked": AppLocalizations.of(context)!.blocked, - "blocked_safebrowsing": AppLocalizations.of(context)!.blockedSafeBrowsingRow, - "blocked_parental": AppLocalizations.of(context)!.blockedParentalRow, - "safe_search": AppLocalizations.of(context)!.safeSearch, - }; return Scaffold( body: NestedScrollView( headerSliverBuilder: (context, innerBoxIsScrolled) => [ SliverOverlapAbsorber( handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar.large( - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - surfaceTintColor: isDesktop(width) ? Colors.transparent : null, - title: Text(AppLocalizations.of(context)!.logs), - expandedHeight: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients != null - ? 170 : null, - actions: [ - if (!(Platform.isAndroid || Platform.isIOS)) IconButton( - onPressed: () => logsProvider.fetchLogs(inOffset: 0), - icon: const Icon(Icons.refresh_rounded), - tooltip: AppLocalizations.of(context)!.refresh, - ), - logsProvider.loadStatus == LoadStatus.loaded - ? IconButton( - onPressed: openFilersModal, - icon: const Icon(Icons.filter_list_rounded), - tooltip: AppLocalizations.of(context)!.filters, - ) - : const SizedBox(), - if (statusProvider.serverStatus != null) IconButton( - tooltip: AppLocalizations.of(context)!.settings, - onPressed: () => { - if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => LogsConfigModal( - onConfirm: updateConfig, - onClear: clearQueries, - dialog: true, - serverVersion: statusProvider.serverStatus!.serverVersion, - ), - barrierDismissible: false - ) - } - else { - showModalBottomSheet( - context: context, - useRootNavigator: true, - builder: (context) => LogsConfigModal( - onConfirm: updateConfig, - onClear: clearQueries, - dialog: false, - serverVersion: statusProvider.serverStatus!.serverVersion, - ), - backgroundColor: Colors.transparent, - isScrollControlled: true - ) - } - }, - icon: const Icon(Icons.settings) - ), - const SizedBox(width: 5), - ], - bottom: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients != null - ? PreferredSize( - preferredSize: const Size(double.maxFinite, 70), - child: Container( - height: 50, - width: double.maxFinite, - padding: const EdgeInsets.only(bottom: 10), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: showDivider == true - ? Theme.of(context).colorScheme.onSurface.withOpacity(0.1) - : Colors.transparent, - ) - ) - ), - child: ListView( - scrollDirection: Axis.horizontal, - children: [ - if (logsProvider.appliedFilters.searchText != null) ...[ - const SizedBox(width: 15), - Chip( - avatar: const Icon( - Icons.search_rounded, - ), - label: Row( - children: [ - Text( - logsProvider.appliedFilters.searchText!, - ), - ], - ), - deleteIcon: const Icon( - Icons.clear, - size: 18, - ), - onDeleted: () { - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus, - searchText: null, - clients: logsProvider.appliedFilters.clients - ) - ); - logsProvider.setSearchText(null); - logsProvider.fetchLogs( - inOffset: 0, - searchText: '' - ); - }, - ), - ], - if (logsProvider.appliedFilters.selectedResultStatus != 'all') ...[ - const SizedBox(width: 15), - Chip( - avatar: const Icon( - Icons.shield_rounded, - ), - label: Row( - children: [ - Text( - translatedString[logsProvider.appliedFilters.selectedResultStatus]!, - ), - ], - ), - deleteIcon: const Icon( - Icons.clear, - size: 18, - ), - onDeleted: () { - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: 'all', - searchText: logsProvider.appliedFilters.searchText, - clients: logsProvider.appliedFilters.clients - ) - ); - logsProvider.setSelectedResultStatus('all'); - logsProvider.fetchLogs( - inOffset: 0, - responseStatus: 'all' - ); - }, - ), - ], - if (logsProvider.appliedFilters.clients != null) ...[ - const SizedBox(width: 15), - Chip( - avatar: const Icon( - Icons.smartphone_rounded, - ), - label: Row( - children: [ - Text( - logsProvider.appliedFilters.clients!.length == 1 - ? logsProvider.appliedFilters.clients![0] - : "${logsProvider.appliedFilters.clients!.length} ${AppLocalizations.of(context)!.clients}", - ), - ], - ), - deleteIcon: const Icon( - Icons.clear, - size: 18, - ), - onDeleted: () { - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus, - searchText: logsProvider.appliedFilters.searchText, - clients: null - ) - ); - logsProvider.setSelectedClients(null); - logsProvider.fetchLogs( - inOffset: 0, - responseStatus: logsProvider.appliedFilters.selectedResultStatus - ); - }, - ), - ], - const SizedBox(width: 15), - ], - ), - ) - ) - : null, - ), + sliver: LogsListAppBar( + innerBoxIsScrolled: innerBoxIsScrolled, + showDivider: showDivider, + ) ) ], body: Builder( diff --git a/lib/screens/logs/logs_list_appbar.dart b/lib/screens/logs/logs_list_appbar.dart new file mode 100644 index 0000000..39b4548 --- /dev/null +++ b/lib/screens/logs/logs_list_appbar.dart @@ -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/screens/logs/logs_config_modal.dart'; +import 'package:adguard_home_manager/screens/logs/logs_filters_modal.dart'; + +import 'package:adguard_home_manager/classes/process_modal.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/functions/compare_versions.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; +import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/models/applied_filters.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; +import 'package:adguard_home_manager/providers/logs_provider.dart'; + +class LogsListAppBar extends StatelessWidget { + final bool innerBoxIsScrolled; + final bool showDivider; + + const LogsListAppBar({ + Key? key, + required this.innerBoxIsScrolled, + required this.showDivider, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final logsProvider = Provider.of(context); + final statusProvider = Provider.of(context); + final serversProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); + + final width = MediaQuery.of(context).size.width; + + void updateConfig(Map data) async { + ProcessModal processModal = ProcessModal(context: context); + processModal.open(AppLocalizations.of(context)!.updatingSettings); + + final result = serverVersionIsAhead( + currentVersion: statusProvider.serverStatus!.serverVersion, + referenceVersion: 'v0.107.28', + referenceVersionBeta: 'v0.108.0-b.33' + ) == true + ? await serversProvider.apiClient!.updateQueryLogParameters(data: data) + : await serversProvider.apiClient!.updateQueryLogParametersLegacy(data: data); + + processModal.close(); + + if (result['result'] == 'success') { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsConfigUpdated, + color: Colors.green + ); + } + else { + appConfigProvider.addLog(result['log']); + + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsConfigNotUpdated, + color: Colors.red + ); + } + } + + void clearQueries() async { + ProcessModal processModal = ProcessModal(context: context); + processModal.open(AppLocalizations.of(context)!.updatingSettings); + + final result = await serversProvider.apiClient!.clearLogs(); + + processModal.close(); + + if (result['result'] == 'success') { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsCleared, + color: Colors.green + ); + } + else { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsNotCleared, + color: Colors.red + ); + } + } + + + void openFilersModal() { + if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (context) => const LogsFiltersModal( + dialog: true, + ), + barrierDismissible: false + ); + } + else { + showModalBottomSheet( + context: context, + useRootNavigator: true, + builder: (context) => const LogsFiltersModal( + dialog: false, + ), + backgroundColor: Colors.transparent, + isScrollControlled: true + ); + } + } + + final Map translatedString = { + "all": AppLocalizations.of(context)!.all, + "filtered": AppLocalizations.of(context)!.filtered, + "processed": AppLocalizations.of(context)!.processedRow, + "whitelisted": AppLocalizations.of(context)!.processedWhitelistRow, + "blocked": AppLocalizations.of(context)!.blocked, + "blocked_safebrowsing": AppLocalizations.of(context)!.blockedSafeBrowsingRow, + "blocked_parental": AppLocalizations.of(context)!.blockedParentalRow, + "safe_search": AppLocalizations.of(context)!.safeSearch, + }; + + return SliverAppBar.large( + pinned: true, + floating: true, + centerTitle: false, + forceElevated: innerBoxIsScrolled, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, + title: Text(AppLocalizations.of(context)!.logs), + expandedHeight: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients != null + ? 170 : null, + actions: [ + if (!(Platform.isAndroid || Platform.isIOS)) IconButton( + onPressed: () => logsProvider.fetchLogs(inOffset: 0), + icon: const Icon(Icons.refresh_rounded), + tooltip: AppLocalizations.of(context)!.refresh, + ), + logsProvider.loadStatus == LoadStatus.loaded + ? IconButton( + onPressed: openFilersModal, + icon: const Icon(Icons.filter_list_rounded), + tooltip: AppLocalizations.of(context)!.filters, + ) + : const SizedBox(), + if (statusProvider.serverStatus != null) IconButton( + tooltip: AppLocalizations.of(context)!.settings, + onPressed: () => { + if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (context) => LogsConfigModal( + onConfirm: updateConfig, + onClear: clearQueries, + dialog: true, + serverVersion: statusProvider.serverStatus!.serverVersion, + ), + barrierDismissible: false + ) + } + else { + showModalBottomSheet( + context: context, + useRootNavigator: true, + builder: (context) => LogsConfigModal( + onConfirm: updateConfig, + onClear: clearQueries, + dialog: false, + serverVersion: statusProvider.serverStatus!.serverVersion, + ), + backgroundColor: Colors.transparent, + isScrollControlled: true + ) + } + }, + icon: const Icon(Icons.settings) + ), + const SizedBox(width: 5), + ], + bottom: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients != null + ? PreferredSize( + preferredSize: const Size(double.maxFinite, 70), + child: Container( + height: 50, + width: double.maxFinite, + padding: const EdgeInsets.only(bottom: 10), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: showDivider == true + ? Theme.of(context).colorScheme.onSurface.withOpacity(0.1) + : Colors.transparent, + ) + ) + ), + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + if (logsProvider.appliedFilters.searchText != null) ...[ + const SizedBox(width: 15), + Chip( + avatar: const Icon( + Icons.search_rounded, + ), + label: Row( + children: [ + Text( + logsProvider.appliedFilters.searchText!, + ), + ], + ), + deleteIcon: const Icon( + Icons.clear, + size: 18, + ), + onDeleted: () { + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus, + searchText: null, + clients: logsProvider.appliedFilters.clients + ) + ); + logsProvider.setSearchText(null); + logsProvider.fetchLogs( + inOffset: 0, + searchText: '' + ); + }, + ), + ], + if (logsProvider.appliedFilters.selectedResultStatus != 'all') ...[ + const SizedBox(width: 15), + Chip( + avatar: const Icon( + Icons.shield_rounded, + ), + label: Row( + children: [ + Text( + translatedString[logsProvider.appliedFilters.selectedResultStatus]!, + ), + ], + ), + deleteIcon: const Icon( + Icons.clear, + size: 18, + ), + onDeleted: () { + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: 'all', + searchText: logsProvider.appliedFilters.searchText, + clients: logsProvider.appliedFilters.clients + ) + ); + logsProvider.setSelectedResultStatus('all'); + logsProvider.fetchLogs( + inOffset: 0, + responseStatus: 'all' + ); + }, + ), + ], + if (logsProvider.appliedFilters.clients != null) ...[ + const SizedBox(width: 15), + Chip( + avatar: const Icon( + Icons.smartphone_rounded, + ), + label: Row( + children: [ + Text( + logsProvider.appliedFilters.clients!.length == 1 + ? logsProvider.appliedFilters.clients![0] + : "${logsProvider.appliedFilters.clients!.length} ${AppLocalizations.of(context)!.clients}", + ), + ], + ), + deleteIcon: const Icon( + Icons.clear, + size: 18, + ), + onDeleted: () { + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus, + searchText: logsProvider.appliedFilters.searchText, + clients: null + ) + ); + logsProvider.setSelectedClients(null); + logsProvider.fetchLogs( + inOffset: 0, + responseStatus: logsProvider.appliedFilters.selectedResultStatus + ); + }, + ), + ], + const SizedBox(width: 15), + ], + ), + ) + ) + : null, + ); + } +} \ No newline at end of file From ad7267bc5cfae8a5b3f21a2329f2db4b2d668657 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 1 Nov 2023 20:46:03 +0100 Subject: [PATCH 057/177] Refactor logs --- .../logs/configuration/config_widgets.dart | 266 +++++++++++ .../logs/configuration/logs_config_modal.dart | 254 +++++++++++ .../{ => details}/log_details_screen.dart | 2 +- .../logs/{ => details}/log_list_tile.dart | 0 .../logs/{ => filters}/clients_modal.dart | 0 .../{ => filters}/filter_status_modal.dart | 0 .../{ => filters}/logs_filters_modal.dart | 4 +- lib/screens/logs/logs.dart | 2 +- lib/screens/logs/logs_config_modal.dart | 413 ------------------ lib/screens/logs/logs_list.dart | 2 +- lib/screens/logs/logs_list_appbar.dart | 4 +- .../settings/dhcp/select_interface_modal.dart | 7 +- 12 files changed, 529 insertions(+), 425 deletions(-) create mode 100644 lib/screens/logs/configuration/config_widgets.dart create mode 100644 lib/screens/logs/configuration/logs_config_modal.dart rename lib/screens/logs/{ => details}/log_details_screen.dart (99%) rename lib/screens/logs/{ => details}/log_list_tile.dart (100%) rename lib/screens/logs/{ => filters}/clients_modal.dart (100%) rename lib/screens/logs/{ => filters}/filter_status_modal.dart (100%) rename lib/screens/logs/{ => filters}/logs_filters_modal.dart (98%) delete mode 100644 lib/screens/logs/logs_config_modal.dart diff --git a/lib/screens/logs/configuration/config_widgets.dart b/lib/screens/logs/configuration/config_widgets.dart new file mode 100644 index 0000000..44523f3 --- /dev/null +++ b/lib/screens/logs/configuration/config_widgets.dart @@ -0,0 +1,266 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/screens/logs/configuration/logs_config_modal.dart'; + +class LogsConfigOptions extends StatelessWidget { + final bool generalSwitch; + final void Function(bool) updateGeneralSwitch; + final bool anonymizeClientIp; + final void Function(bool) updateAnonymizeClientIp; + final List retentionItems; + final String? retentionTime; + final void Function(String?) updateRetentionTime; + final void Function() onClear; + final void Function() onConfirm; + + const LogsConfigOptions({ + Key? key, + required this.generalSwitch, + required this.updateGeneralSwitch, + required this.anonymizeClientIp, + required this.updateAnonymizeClientIp, + required this.retentionItems, + required this.retentionTime, + required this.updateRetentionTime, + required this.onClear, + required this.onConfirm + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SingleChildScrollView( + child: Wrap( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 24), + child: Icon( + Icons.settings, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.logsSettings, + 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: Material( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(28), + child: InkWell( + onTap: () => updateGeneralSwitch(!generalSwitch), + borderRadius: BorderRadius.circular(28), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 8 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.enableLog, + style: const TextStyle( + fontSize: 18, + ), + ), + Switch( + value: generalSwitch, + onChanged: updateGeneralSwitch, + ) + ], + ), + ), + ), + ), + ), + Container(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 14), + child: Column( + children: [ + Material( + color: Colors.transparent, + child: InkWell( + onTap: () => updateAnonymizeClientIp(!anonymizeClientIp), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.anonymizeClientIp, + style: const TextStyle( + fontSize: 16 + ), + ), + Switch( + value: anonymizeClientIp, + onChanged: updateAnonymizeClientIp, + ) + ], + ), + ), + ), + ), + Container(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: DropdownButtonFormField( + items: retentionItems.map>((item) { + return DropdownMenuItem( + value: item.value.toString(), + child: Text(item.label), + ); + }).toList(), + value: retentionTime, + onChanged: (value) => updateRetentionTime(value), + decoration: InputDecoration( + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + label: Text(AppLocalizations.of(context)!.retentionTime) + ), + borderRadius: BorderRadius.circular(20), + ), + ), + ], + ), + ) + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () { + Navigator.pop(context); + onClear(); + }, + child: Text(AppLocalizations.of(context)!.clearLogs) + ), + Row( + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ), + const SizedBox(width: 20), + TextButton( + onPressed: retentionTime != '' + ? () { + Navigator.pop(context); + onConfirm(); + } + : null, + child: Text( + AppLocalizations.of(context)!.confirm, + style: TextStyle( + color: retentionTime != '' + ? Theme.of(context).colorScheme.primary + : Colors.grey + ), + ) + ), + ], + ) + ], + ), + ), + if (Platform.isIOS) const SizedBox(height: 16) + ], + ); + } +} + +class ConfigLogsLoading extends StatelessWidget { + const ConfigLogsLoading({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + AppLocalizations.of(context)!.loadingLogsSettings, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ) + ], + ), + ); + } +} + +class ConfigLogsError extends StatelessWidget { + const ConfigLogsError({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + AppLocalizations.of(context)!.logSettingsNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ) + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/logs/configuration/logs_config_modal.dart b/lib/screens/logs/configuration/logs_config_modal.dart new file mode 100644 index 0000000..698348c --- /dev/null +++ b/lib/screens/logs/configuration/logs_config_modal.dart @@ -0,0 +1,254 @@ +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/logs/configuration/config_widgets.dart'; + +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/functions/compare_versions.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; + +class RetentionItem { + final String label; + final double value; + + const RetentionItem({ + required this.label, + required this.value, + }); +} + +class LogsConfigModal extends StatelessWidget { + final void Function(Map) onConfirm; + final void Function() onClear; + final bool dialog; + final String serverVersion; + + const LogsConfigModal({ + Key? key, + required this.onConfirm, + required this.onClear, + required this.dialog, + required this.serverVersion + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final serversProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); + + return LogsConfigModalWidget( + serversProvider: serversProvider, + appConfigProvider: appConfigProvider, + context: context, + onConfirm: onConfirm, + onClear: onClear, + dialog: dialog, + serverVersion: serverVersion, + ); + } +} + +class LogsConfigModalWidget extends StatefulWidget { + final ServersProvider serversProvider; + final AppConfigProvider appConfigProvider; + final BuildContext context; + final void Function(Map) onConfirm; + final void Function() onClear; + final bool dialog; + final String serverVersion; + + const LogsConfigModalWidget({ + Key? key, + required this.serversProvider, + required this.appConfigProvider, + required this.context, + required this.onConfirm, + required this.onClear, + required this.dialog, + required this.serverVersion + }) : super(key: key); + + @override + State createState() => _LogsConfigModalWidgetState(); +} + +class _LogsConfigModalWidgetState extends State { + bool generalSwitch = false; + bool anonymizeClientIp = false; + String? retentionTime = ""; + + List retentionItems = []; + + LoadStatus loadStatus = LoadStatus.loading; + + void loadData() async { + final serversProvider = Provider.of(context, listen: false); + + final result = serverVersionIsAhead( + currentVersion: widget.serverVersion, + referenceVersion: 'v0.107.28', + referenceVersionBeta: 'v0.108.0-b.33' + ) == true + ? await serversProvider.apiClient!.getQueryLogInfo() + : await serversProvider.apiClient!.getQueryLogInfoLegacy(); + + if (mounted) { + if (result['result'] == 'success') { + setState(() { + generalSwitch = result['data']['enabled']; + anonymizeClientIp = result['data']['anonymize_client_ip']; + retentionTime = result['data']['interval'].toString(); + loadStatus = LoadStatus.loading; + }); + } + else { + setState(() => loadStatus = LoadStatus.error); + } + } + } + + @override + void initState() { + retentionItems = serverVersionIsAhead( + currentVersion: widget.serverVersion, + referenceVersion: 'v0.107.28', + referenceVersionBeta: 'v0.108.0-b.33' + ) == true ? [ + RetentionItem( + label: AppLocalizations.of(widget.context)!.hours6, + value: 21600000 + ), + RetentionItem( + label: AppLocalizations.of(widget.context)!.hours24, + value: 86400000 + ), + RetentionItem( + label: AppLocalizations.of(widget.context)!.days7, + value: 604800000 + ), + RetentionItem( + label: AppLocalizations.of(widget.context)!.days30, + value: 2592000000 + ), + RetentionItem( + label: AppLocalizations.of(widget.context)!.days90, + value: 7776000000 + ), + ] : [ + RetentionItem( + label: AppLocalizations.of(widget.context)!.hours6, + value: 0.25 + ), + RetentionItem( + label: AppLocalizations.of(widget.context)!.hours24, + value: 1 + ), + RetentionItem( + label: AppLocalizations.of(widget.context)!.days7, + value: 7 + ), + RetentionItem( + label: AppLocalizations.of(widget.context)!.days30, + value: 30 + ), + RetentionItem( + label: AppLocalizations.of(widget.context)!.days90, + value: 90 + ), + ]; + + loadData(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + if (widget.dialog == true) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 500 + ), + child: Builder( + builder: (context) { + switch (loadStatus) { + case LoadStatus.loading: + return const ConfigLogsLoading(); + + case LoadStatus.loaded: + return LogsConfigOptions( + generalSwitch: generalSwitch, + updateGeneralSwitch: (v) => setState(() => generalSwitch = v), + anonymizeClientIp: anonymizeClientIp, + updateAnonymizeClientIp: (v) => setState(() => anonymizeClientIp = v), + retentionItems: retentionItems, + retentionTime: retentionTime, + updateRetentionTime: (v) => setState(() => retentionTime = v), + onClear: () => widget.onClear(), + onConfirm: () => widget.onConfirm({ + "enabled": generalSwitch, + "interval": double.parse(retentionTime!), + "anonymize_client_ip": anonymizeClientIp + }) + ); + + case LoadStatus.error: + return const ConfigLogsError(); + + default: + return const SizedBox(); + } + }, + ) + ), + ); + } + else { + return Container( + height: Platform.isIOS ? 436 : 420, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) + ), + color: Theme.of(context).dialogBackgroundColor + ), + child: Builder( + builder: (context) { + switch (loadStatus) { + case LoadStatus.loading: + return const ConfigLogsLoading(); + + case LoadStatus.loaded: + return LogsConfigOptions( + generalSwitch: generalSwitch, + updateGeneralSwitch: (v) => setState(() => generalSwitch = v), + anonymizeClientIp: anonymizeClientIp, + updateAnonymizeClientIp: (v) => setState(() => anonymizeClientIp = v), + retentionItems: retentionItems, + retentionTime: retentionTime, + updateRetentionTime: (v) => setState(() => retentionTime = v), + onClear: () => widget.onClear(), + onConfirm: () => widget.onConfirm({ + "enabled": generalSwitch, + "interval": double.parse(retentionTime!), + "anonymize_client_ip": anonymizeClientIp + }) + ); + + case LoadStatus.error: + return const ConfigLogsError(); + + default: + return const SizedBox(); + } + }, + ) + ); + } + } +} \ No newline at end of file diff --git a/lib/screens/logs/log_details_screen.dart b/lib/screens/logs/details/log_details_screen.dart similarity index 99% rename from lib/screens/logs/log_details_screen.dart rename to lib/screens/logs/details/log_details_screen.dart index 161e9fa..510c2e2 100644 --- a/lib/screens/logs/log_details_screen.dart +++ b/lib/screens/logs/details/log_details_screen.dart @@ -6,7 +6,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; -import 'package:adguard_home_manager/screens/logs/log_list_tile.dart'; +import 'package:adguard_home_manager/screens/logs/details/log_list_tile.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/open_url.dart'; diff --git a/lib/screens/logs/log_list_tile.dart b/lib/screens/logs/details/log_list_tile.dart similarity index 100% rename from lib/screens/logs/log_list_tile.dart rename to lib/screens/logs/details/log_list_tile.dart diff --git a/lib/screens/logs/clients_modal.dart b/lib/screens/logs/filters/clients_modal.dart similarity index 100% rename from lib/screens/logs/clients_modal.dart rename to lib/screens/logs/filters/clients_modal.dart diff --git a/lib/screens/logs/filter_status_modal.dart b/lib/screens/logs/filters/filter_status_modal.dart similarity index 100% rename from lib/screens/logs/filter_status_modal.dart rename to lib/screens/logs/filters/filter_status_modal.dart diff --git a/lib/screens/logs/logs_filters_modal.dart b/lib/screens/logs/filters/logs_filters_modal.dart similarity index 98% rename from lib/screens/logs/logs_filters_modal.dart rename to lib/screens/logs/filters/logs_filters_modal.dart index e41efa5..8e1cdbe 100644 --- a/lib/screens/logs/logs_filters_modal.dart +++ b/lib/screens/logs/filters/logs_filters_modal.dart @@ -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/logs/clients_modal.dart'; -import 'package:adguard_home_manager/screens/logs/filter_status_modal.dart'; +import 'package:adguard_home_manager/screens/logs/filters/clients_modal.dart'; +import 'package:adguard_home_manager/screens/logs/filters/filter_status_modal.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; diff --git a/lib/screens/logs/logs.dart b/lib/screens/logs/logs.dart index 863f93d..c1510c3 100644 --- a/lib/screens/logs/logs.dart +++ b/lib/screens/logs/logs.dart @@ -4,7 +4,7 @@ import 'package:adguard_home_manager/models/logs.dart'; import 'package:flutter/material.dart'; import 'package:adguard_home_manager/screens/logs/logs_list.dart'; -import 'package:adguard_home_manager/screens/logs/log_details_screen.dart'; +import 'package:adguard_home_manager/screens/logs/details/log_details_screen.dart'; class Logs extends StatefulWidget { const Logs({Key? key}) : super(key: key); diff --git a/lib/screens/logs/logs_config_modal.dart b/lib/screens/logs/logs_config_modal.dart deleted file mode 100644 index 2f16721..0000000 --- a/lib/screens/logs/logs_config_modal.dart +++ /dev/null @@ -1,413 +0,0 @@ -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/functions/compare_versions.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; - -class LogsConfigModal extends StatelessWidget { - final void Function(Map) onConfirm; - final void Function() onClear; - final bool dialog; - final String serverVersion; - - const LogsConfigModal({ - Key? key, - required this.onConfirm, - required this.onClear, - required this.dialog, - required this.serverVersion - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final serversProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - - return LogsConfigModalWidget( - serversProvider: serversProvider, - appConfigProvider: appConfigProvider, - context: context, - onConfirm: onConfirm, - onClear: onClear, - dialog: dialog, - serverVersion: serverVersion, - ); - } -} - -class LogsConfigModalWidget extends StatefulWidget { - final ServersProvider serversProvider; - final AppConfigProvider appConfigProvider; - final BuildContext context; - final void Function(Map) onConfirm; - final void Function() onClear; - final bool dialog; - final String serverVersion; - - const LogsConfigModalWidget({ - Key? key, - required this.serversProvider, - required this.appConfigProvider, - required this.context, - required this.onConfirm, - required this.onClear, - required this.dialog, - required this.serverVersion - }) : super(key: key); - - @override - State createState() => _LogsConfigModalWidgetState(); -} - -class _LogsConfigModalWidgetState extends State { - bool generalSwitch = false; - bool anonymizeClientIp = false; - String? retentionTime = ""; - - List> retentionItems = []; - - int loadStatus = 0; - - void loadData() async { - final serversProvider = Provider.of(context, listen: false); - - final result = serverVersionIsAhead( - currentVersion: widget.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == true - ? await serversProvider.apiClient!.getQueryLogInfo() - : await serversProvider.apiClient!.getQueryLogInfoLegacy(); - - if (mounted) { - if (result['result'] == 'success') { - setState(() { - generalSwitch = result['data']['enabled']; - anonymizeClientIp = result['data']['anonymize_client_ip']; - retentionTime = result['data']['interval'].toString(); - loadStatus = 1; - }); - } - else { - setState(() => loadStatus = 2); - } - } - } - - @override - void initState() { - retentionItems = serverVersionIsAhead( - currentVersion: widget.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == true ? [ - { - 'label': AppLocalizations.of(widget.context)!.hours6, - 'value': 21600000 - }, - { - 'label': AppLocalizations.of(widget.context)!.hours24, - 'value': 86400000 - }, - { - 'label': AppLocalizations.of(widget.context)!.days7, - 'value': 604800000 - }, - { - 'label': AppLocalizations.of(widget.context)!.days30, - 'value': 2592000000 - }, - { - 'label': AppLocalizations.of(widget.context)!.days90, - 'value': 7776000000 - }, - ] : [ - { - 'label': AppLocalizations.of(widget.context)!.hours6, - 'value': 0.25 - }, - { - 'label': AppLocalizations.of(widget.context)!.hours24, - 'value': 1 - }, - { - 'label': AppLocalizations.of(widget.context)!.days7, - 'value': 7 - }, - { - 'label': AppLocalizations.of(widget.context)!.days30, - 'value': 30 - }, - { - 'label': AppLocalizations.of(widget.context)!.days90, - 'value': 90 - }, - ]; - - loadData(); - super.initState(); - } - - @override - Widget build(BuildContext context) { - Widget generateBody() { - switch (loadStatus) { - case 0: - return Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - AppLocalizations.of(context)!.loadingLogsSettings, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ) - ], - ), - ); - - case 1: - 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.settings, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.logsSettings, - 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: Material( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(28), - child: InkWell( - onTap: () => setState(() => generalSwitch = !generalSwitch), - borderRadius: BorderRadius.circular(28), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 8 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.enableLog, - style: const TextStyle( - fontSize: 18, - ), - ), - Switch( - value: generalSwitch, - onChanged: (value) => setState(() => generalSwitch = value), - ) - ], - ), - ), - ), - ), - ), - Container(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 14), - child: Column( - children: [ - Material( - color: Colors.transparent, - child: InkWell( - onTap: () => setState(() => anonymizeClientIp = !anonymizeClientIp), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.anonymizeClientIp, - style: const TextStyle( - fontSize: 16 - ), - ), - Switch( - value: anonymizeClientIp, - onChanged: (value) => setState(() => anonymizeClientIp = value), - ) - ], - ), - ), - ), - ), - Container(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: DropdownButtonFormField( - items: retentionItems.map>((Map item) { - return DropdownMenuItem( - value: item['value'].toString(), - child: Text(item['label']), - ); - }).toList(), - value: retentionTime, - onChanged: (value) => setState(() => retentionTime = value), - decoration: InputDecoration( - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - label: Text(AppLocalizations.of(context)!.retentionTime) - ), - borderRadius: BorderRadius.circular(20), - ), - ), - ], - ), - ) - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.all(24), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: () { - Navigator.pop(context); - widget.onClear(); - }, - child: Text(AppLocalizations.of(context)!.clearLogs) - ), - Row( - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) - ), - const SizedBox(width: 20), - TextButton( - onPressed: retentionTime != '' - ? () { - Navigator.pop(context); - widget.onConfirm({ - "enabled": generalSwitch, - "interval": double.parse(retentionTime!), - "anonymize_client_ip": anonymizeClientIp - }); - } - : null, - child: Text( - AppLocalizations.of(context)!.confirm, - style: TextStyle( - color: retentionTime != '' - ? Theme.of(context).colorScheme.primary - : Colors.grey - ), - ) - ), - ], - ) - ], - ), - ), - if (Platform.isIOS) const SizedBox(height: 16) - ], - ); - - case 2: - return Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - AppLocalizations.of(context)!.logSettingsNotLoaded, - 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: 500 - ), - child: generateBody() - ), - ); - } - else { - return Container( - height: Platform.isIOS ? 436 : 420, - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28) - ), - color: Theme.of(context).dialogBackgroundColor - ), - child: generateBody() - ); - } - } -} \ No newline at end of file diff --git a/lib/screens/logs/logs_list.dart b/lib/screens/logs/logs_list.dart index 72978ce..ac0596a 100644 --- a/lib/screens/logs/logs_list.dart +++ b/lib/screens/logs/logs_list.dart @@ -4,7 +4,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/logs/log_details_screen.dart'; +import 'package:adguard_home_manager/screens/logs/details/log_details_screen.dart'; import 'package:adguard_home_manager/screens/logs/log_tile.dart'; import 'package:adguard_home_manager/screens/logs/logs_list_appbar.dart'; diff --git a/lib/screens/logs/logs_list_appbar.dart b/lib/screens/logs/logs_list_appbar.dart index 39b4548..8e02aed 100644 --- a/lib/screens/logs/logs_list_appbar.dart +++ b/lib/screens/logs/logs_list_appbar.dart @@ -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/logs/logs_config_modal.dart'; -import 'package:adguard_home_manager/screens/logs/logs_filters_modal.dart'; +import 'package:adguard_home_manager/screens/logs/filters/logs_filters_modal.dart'; +import 'package:adguard_home_manager/screens/logs/configuration/logs_config_modal.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/constants/enums.dart'; diff --git a/lib/screens/settings/dhcp/select_interface_modal.dart b/lib/screens/settings/dhcp/select_interface_modal.dart index 668548d..0a740fd 100644 --- a/lib/screens/settings/dhcp/select_interface_modal.dart +++ b/lib/screens/settings/dhcp/select_interface_modal.dart @@ -90,9 +90,7 @@ class SelectInterfaceModal extends StatelessWidget { } else { return GestureDetector( - onTap: () => Navigator.of(context).pop(), - child: Container( - color: Colors.transparent, + onTap: () => Navigator.of(context).pop(), child: DraggableScrollableSheet( initialChildSize: 0.6, minChildSize: 0.3, @@ -154,8 +152,7 @@ class SelectInterfaceModal extends StatelessWidget { ); }, ), - ), - ); + ); } } } \ No newline at end of file From 2837f8543504c10059f67abc45acfa52ae6f3d23 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 1 Nov 2023 20:47:37 +0100 Subject: [PATCH 058/177] Added animation top items --- .../clients/client/logs_list_client.dart | 2 +- lib/screens/home/home.dart | 2 +- lib/screens/home/top_items.dart | 447 ------------------ lib/screens/home/top_items/row_item.dart | 260 ++++++++++ lib/screens/home/top_items/top_items.dart | 299 ++++++++++++ lib/widgets/custom_pie_chart.dart | 4 +- 6 files changed, 564 insertions(+), 450 deletions(-) delete mode 100644 lib/screens/home/top_items.dart create mode 100644 lib/screens/home/top_items/row_item.dart create mode 100644 lib/screens/home/top_items/top_items.dart diff --git a/lib/screens/clients/client/logs_list_client.dart b/lib/screens/clients/client/logs_list_client.dart index 9761e3f..8d975a9 100644 --- a/lib/screens/clients/client/logs_list_client.dart +++ b/lib/screens/clients/client/logs_list_client.dart @@ -6,7 +6,7 @@ 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/screens/logs/details/log_details_screen.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/models/logs.dart'; diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index 293eef3..343d065 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -11,7 +11,7 @@ import 'package:adguard_home_manager/screens/home/server_status.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/top_items/top_items.dart'; import 'package:adguard_home_manager/screens/home/chart.dart'; import 'package:adguard_home_manager/functions/number_format.dart'; diff --git a/lib/screens/home/top_items.dart b/lib/screens/home/top_items.dart deleted file mode 100644 index ebf4e94..0000000 --- a/lib/screens/home/top_items.dart +++ /dev/null @@ -1,447 +0,0 @@ -// 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/widgets/custom_pie_chart.dart'; -import 'package:adguard_home_manager/widgets/domain_options.dart'; -import 'package:adguard_home_manager/screens/top_items/top_items_modal.dart'; -import 'package:adguard_home_manager/screens/top_items/top_items.dart'; - -import 'package:adguard_home_manager/models/applied_filters.dart'; -import 'package:adguard_home_manager/providers/status_provider.dart'; -import 'package:adguard_home_manager/providers/logs_provider.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; - -class TopItems extends StatefulWidget { - final String type; - final String label; - final List> data; - final bool? clients; - - const TopItems({ - Key? key, - required this.type, - required this.label, - required this.data, - this.clients - }) : super(key: key); - - @override - State createState() => _TopItemsState(); -} - -class _TopItemsState extends State { - bool _showChart = true; - - final colors = [ - Colors.red, - Colors.green, - Colors.blue, - Colors.orange, - Colors.teal, - Colors.grey - ]; - - @override - void initState() { - _showChart = Provider.of(context, listen: false).showTopItemsChart; - super.initState(); - } - - @override - Widget build(BuildContext context) { - final statusProvider = Provider.of(context); - - final width = MediaQuery.of(context).size.width; - - List> generateData() { - switch (widget.type) { - case 'topQueriedDomains': - return statusProvider.serverStatus!.stats.topQueriedDomains; - - case 'topBlockedDomains': - return statusProvider.serverStatus!.stats.topBlockedDomains; - - case 'topClients': - return statusProvider.serverStatus!.stats.topClients; - - default: - return []; - } - } - - Map chartData() { - Map values = {}; - widget.data.sublist(0, widget.data.length > 5 ? 5 : widget.data.length).forEach((element) { - values = { - ...values, - element.keys.first: element.values.first.toDouble() - }; - }); - if (widget.data.length > 5) { - final int rest = List.from( - widget.data.sublist(5, widget.data.length).map((e) => e.values.first.toInt()) - ).reduce((a, b) => a + b); - values = { - ...values, - AppLocalizations.of(context)!.others: rest.toDouble() - }; - } - return values; - } - - final List itemsList = widget.data.sublist( - 0, - widget.data.length > 5 ? 5 : widget.data.length - ).asMap().entries.map((e) => RowItem( - clients: widget.clients ?? false, - domain: e.value.keys.toList()[0], - number: e.value.values.toList()[0].toString(), - type: widget.type, - chartColor: _showChart ? colors[e.key] : null, - )).toList(); - - final Widget noItems = Padding( - padding: const EdgeInsets.only( - bottom: 20, - top: 10 - ), - child: Text( - AppLocalizations.of(context)!.noItems, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ); - - final Widget chart = CustomPieChart( - data: chartData(), - colors: colors - ); - - return SizedBox( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - mainAxisAlignment: width <= 700 - ? MainAxisAlignment.spaceBetween - : MainAxisAlignment.center, - children: [ - Text( - widget.label, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface - ), - ), - if (width <= 700) TextButton( - onPressed: () => setState(() => _showChart = !_showChart), - child: Text( - _showChart - ? AppLocalizations.of(context)!.hideChart - : AppLocalizations.of(context)!.showChart - ) - ) - ], - ), - ), - - const SizedBox(height: 24), - - if (widget.data.isEmpty) noItems, - if (widget.data.isNotEmpty && width > 700) Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - flex: 1, - child: ConstrainedBox( - constraints: const BoxConstraints( - maxHeight: 250 - ), - child: Padding( - padding: const EdgeInsets.all(16), - child: chart, - ), - ) - ), - Expanded( - flex: 2, - child: Column( - children: [ - ...itemsList, - OthersRowItem(items: widget.data) - ] - ), - ) - ], - ), - if (widget.data.isNotEmpty && width <= 700) ...[ - if (_showChart) ...[ - SizedBox( - height: 150, - child: chart - ), - const SizedBox(height: 16), - ], - ...itemsList, - if (_showChart) OthersRowItem(items: widget.data), - const SizedBox(height: 16), - ], - - if (widget.data.length > 5) ...[ - Padding( - padding: const EdgeInsets.only(right: 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => { - if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => TopItemsModal( - type: widget.type, - title: widget.label, - isClient: widget.clients, - data: generateData(), - ) - ) - } - else { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => TopItemsScreen( - type: widget.type, - title: widget.label, - isClient: widget.clients, - data: generateData(), - ) - ) - ) - } - }, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text(AppLocalizations.of(context)!.viewMore), - const SizedBox(width: 10), - const Icon( - Icons.arrow_forward, - size: 20, - ) - ], - ) - ), - ], - ), - ), - const SizedBox(height: 10), - ] - ], - ), - ); - } -} - -class RowItem extends StatelessWidget { - final String type; - final Color? chartColor; - final String domain; - final String number; - final bool clients; - - const RowItem({ - Key? key, - required this.type, - this.chartColor, - required this.domain, - required this.number, - required this.clients - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final statusProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - final logsProvider = Provider.of(context); - - String? name; - if (clients == true) { - try { - name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(domain)).name; - } catch (e) { - // ---- // - } - } - - return Material( - color: Colors.transparent, - child: DomainOptions( - item: domain, - isClient: type == 'topClients', - isBlocked: type == 'topBlockedDomains', - onTap: () { - if (type == 'topQueriedDomains' || type == 'topBlockedDomains') { - logsProvider.setSearchText(domain); - logsProvider.setSelectedClients(null); - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: 'all', - searchText: domain, - clients: null - ) - ); - appConfigProvider.setSelectedScreen(2); - } - else if (type == 'topClients') { - logsProvider.setSearchText(null); - logsProvider.setSelectedClients([domain]); - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: 'all', - searchText: null, - clients: [domain] - ) - ); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 8 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Row( - children: [ - if (chartColor != null) Container( - margin: const EdgeInsets.only(right: 16), - width: 12, - height: 12, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - color: chartColor - ), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - domain, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - if (name != null) ...[ - const SizedBox(height: 5), - Text( - name, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ] - ], - ), - ), - ], - ), - ), - const SizedBox(width: 16), - Text( - number, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ) - ], - ), - ), - ), - ); - } -} - -class OthersRowItem extends StatelessWidget { - final List> items; - - const OthersRowItem({ - Key? key, - required this.items - }) : super(key: key); - - @override - Widget build(BuildContext context) { - - if (items.length <= 5) { - return const SizedBox(); - } - - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 8 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Row( - children: [ - Container( - margin: const EdgeInsets.only(right: 16), - width: 12, - height: 12, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - color: Colors.grey - ), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.others, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ), - ), - ], - ), - ), - const SizedBox(width: 16), - Text( - List.from( - items.sublist(5, items.length).map((e) => e.values.first.toInt()) - ).reduce((a, b) => a + b).toString(), - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ) - ], - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/home/top_items/row_item.dart b/lib/screens/home/top_items/row_item.dart new file mode 100644 index 0000000..8985fd6 --- /dev/null +++ b/lib/screens/home/top_items/row_item.dart @@ -0,0 +1,260 @@ +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/domain_options.dart'; + +import 'package:adguard_home_manager/models/applied_filters.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/providers/logs_provider.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; + +class RowItem extends StatelessWidget { + final String type; + final Color chartColor; + final String domain; + final String number; + final bool clients; + final bool showColor; + + const RowItem({ + Key? key, + required this.type, + required this.chartColor, + required this.domain, + required this.number, + required this.clients, + required this.showColor, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final statusProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); + final logsProvider = Provider.of(context); + + String? name; + if (clients == true) { + try { + name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(domain)).name; + } catch (e) { + // ---- // + } + } + + return Material( + color: Colors.transparent, + child: DomainOptions( + item: domain, + isClient: type == 'topClients', + isBlocked: type == 'topBlockedDomains', + onTap: () { + if (type == 'topQueriedDomains' || type == 'topBlockedDomains') { + logsProvider.setSearchText(domain); + logsProvider.setSelectedClients(null); + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: 'all', + searchText: domain, + clients: null + ) + ); + appConfigProvider.setSelectedScreen(2); + } + else if (type == 'topClients') { + logsProvider.setSearchText(null); + logsProvider.setSelectedClients([domain]); + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: 'all', + searchText: null, + clients: [domain] + ) + ); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 8 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Row( + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 200), + curve: Curves.ease, + margin: EdgeInsets.only(right: showColor ? 16 : 0), + width: showColor ? 12 : 0, + height: showColor ? 12 : 0, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: chartColor + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + domain, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + if (name != null) ...[ + const SizedBox(height: 5), + Text( + name, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ] + ], + ), + ), + ], + ), + ), + const SizedBox(width: 16), + Text( + number, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + ), + ), + ); + } +} + +class OthersRowItem extends StatefulWidget { + final List> items; + final bool showColor; + + const OthersRowItem({ + Key? key, + required this.items, + required this.showColor, + }) : super(key: key); + + @override + State createState() => _OthersRowItemState(); +} + +class _OthersRowItemState extends State with SingleTickerProviderStateMixin { + late AnimationController expandController; + late Animation animation; + + @override + void initState() { + super.initState(); + prepareAnimations(); + _runExpandCheck(); + } + + void prepareAnimations() { + expandController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 200) + ); + animation = CurvedAnimation( + parent: expandController, + curve: Curves.ease, + ); + } + + void _runExpandCheck() { + if (widget.showColor) { + expandController.forward(); + } + else { + expandController.reverse(); + } + } + + @override + void didUpdateWidget(oldWidget) { + super.didUpdateWidget(oldWidget); + _runExpandCheck(); + } + + @override + void dispose() { + expandController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (widget.items.length <= 5) { + return const SizedBox(); + } + + return SizeTransition( + axisAlignment: 1.0, + sizeFactor: animation, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 8 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Row( + children: [ + Container( + margin: const EdgeInsets.only(right: 16), + width: 12, + height: 12, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: Colors.grey + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.others, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ), + ], + ), + ), + const SizedBox(width: 16), + Text( + List.from( + widget.items.sublist(5, widget.items.length).map((e) => e.values.first.toInt()) + ).reduce((a, b) => a + b).toString(), + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/home/top_items/top_items.dart b/lib/screens/home/top_items/top_items.dart new file mode 100644 index 0000000..97b61e0 --- /dev/null +++ b/lib/screens/home/top_items/top_items.dart @@ -0,0 +1,299 @@ +// 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/home/top_items/row_item.dart'; +import 'package:adguard_home_manager/widgets/custom_pie_chart.dart'; +import 'package:adguard_home_manager/screens/top_items/top_items_modal.dart'; +import 'package:adguard_home_manager/screens/top_items/top_items.dart'; + +import 'package:adguard_home_manager/providers/status_provider.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; + +class TopItems extends StatefulWidget { + final String type; + final String label; + final List> data; + final bool? clients; + + const TopItems({ + Key? key, + required this.type, + required this.label, + required this.data, + this.clients + }) : super(key: key); + + @override + State createState() => _TopItemsState(); +} + +class _TopItemsState extends State { + bool _showChart = true; + + final colors = [ + Colors.red, + Colors.green, + Colors.blue, + Colors.orange, + Colors.teal, + Colors.grey + ]; + + @override + void initState() { + _showChart = Provider.of(context, listen: false).showTopItemsChart; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final statusProvider = Provider.of(context); + + final width = MediaQuery.of(context).size.width; + + List> generateData() { + switch (widget.type) { + case 'topQueriedDomains': + return statusProvider.serverStatus!.stats.topQueriedDomains; + + case 'topBlockedDomains': + return statusProvider.serverStatus!.stats.topBlockedDomains; + + case 'topClients': + return statusProvider.serverStatus!.stats.topClients; + + default: + return []; + } + } + + Map chartData() { + Map values = {}; + widget.data.sublist(0, widget.data.length > 5 ? 5 : widget.data.length).forEach((element) { + values = { + ...values, + element.keys.first: element.values.first.toDouble() + }; + }); + if (widget.data.length > 5) { + final int rest = List.from( + widget.data.sublist(5, widget.data.length).map((e) => e.values.first.toInt()) + ).reduce((a, b) => a + b); + values = { + ...values, + AppLocalizations.of(context)!.others: rest.toDouble() + }; + } + return values; + } + + final Widget noItems = Padding( + padding: const EdgeInsets.only( + bottom: 20, + top: 10 + ), + child: Text( + AppLocalizations.of(context)!.noItems, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ); + + return SizedBox( + child: Column( + children: [ + if (widget.data.isEmpty) noItems, + if (widget.data.isNotEmpty && width > 700) Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + flex: 1, + child: ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 250 + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: CustomPieChart( + data: chartData(), + colors: colors + ) + ), + ) + ), + Expanded( + flex: 2, + child: Column( + children: [ + ItemsList( + colors: colors, + data: widget.data, + clients: widget.clients, + type: widget.type, + showChart: _showChart + ), + OthersRowItem( + items: widget.data, + showColor: true, + ) + ] + ), + ) + ], + ), + if (widget.data.isNotEmpty && width <= 700) ...[ + ExpansionPanelList( + expandedHeaderPadding: const EdgeInsets.all(0), + elevation: 0, + expansionCallback: (_, isExpanded) => setState(() => _showChart = isExpanded), + children: [ + ExpansionPanel( + headerBuilder: (context, isExpanded) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + mainAxisAlignment: width <= 700 + ? MainAxisAlignment.spaceBetween + : MainAxisAlignment.center, + children: [ + Text( + widget.label, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ), + body: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + children: [ + SizedBox( + height: 150, + child: CustomPieChart( + data: chartData(), + colors: colors + ) + ), + const SizedBox(height: 16), + ], + ), + ), + isExpanded: _showChart + ), + ], + ), + Padding( + padding: const EdgeInsets.only(top: 8), + child: ItemsList( + colors: colors, + data: widget.data, + clients: widget.clients, + type: widget.type, + showChart: _showChart + ), + ), + OthersRowItem( + items: widget.data, + showColor: _showChart, + ), + const SizedBox(height: 16), + ], + + if (widget.data.length > 5) ...[ + Padding( + padding: const EdgeInsets.only(right: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => { + if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => TopItemsModal( + type: widget.type, + title: widget.label, + isClient: widget.clients, + data: generateData(), + ) + ) + } + else { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TopItemsScreen( + type: widget.type, + title: widget.label, + isClient: widget.clients, + data: generateData(), + ) + ) + ) + } + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(AppLocalizations.of(context)!.viewMore), + const SizedBox(width: 10), + const Icon( + Icons.arrow_forward, + size: 20, + ) + ], + ) + ), + ], + ), + ), + const SizedBox(height: 10), + ] + ], + ), + ); + } +} + +class ItemsList extends StatelessWidget { + final List colors; + final List> data; + final bool? clients; + final String type; + final bool showChart; + + const ItemsList({ + Key? key, + required this.colors, + required this.data, + required this.clients, + required this.type, + required this.showChart, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: data.sublist( + 0, data.length > 5 ? 5 : data.length + ).asMap().entries.map((e) => RowItem( + clients: clients ?? false, + domain: e.value.keys.toList()[0], + number: e.value.values.toList()[0].toString(), + type: type, + chartColor: colors[e.key], + showColor: showChart, + )).toList() + ); + } +} \ No newline at end of file diff --git a/lib/widgets/custom_pie_chart.dart b/lib/widgets/custom_pie_chart.dart index 6af7e11..0c8f9f6 100644 --- a/lib/widgets/custom_pie_chart.dart +++ b/lib/widgets/custom_pie_chart.dart @@ -4,18 +4,20 @@ import 'package:pie_chart/pie_chart.dart'; class CustomPieChart extends StatelessWidget { final Map data; final List colors; + final Duration? animationDuration; const CustomPieChart({ Key? key, required this.data, required this.colors, + this.animationDuration = const Duration(milliseconds: 800), }) : super(key: key); @override Widget build(BuildContext context) { return PieChart( dataMap: data, - animationDuration: const Duration(milliseconds: 800), + animationDuration: animationDuration, colorList: colors, initialAngleInDegree: 270, chartType: ChartType.ring, From 1eb0df9063a9f864dff037db4df8d73d1c31503b Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 1 Nov 2023 20:49:38 +0100 Subject: [PATCH 059/177] Fix filter clients from top items --- lib/screens/home/top_items/row_item.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/screens/home/top_items/row_item.dart b/lib/screens/home/top_items/row_item.dart index 8985fd6..b22b33b 100644 --- a/lib/screens/home/top_items/row_item.dart +++ b/lib/screens/home/top_items/row_item.dart @@ -71,6 +71,7 @@ class RowItem extends StatelessWidget { clients: [domain] ) ); + appConfigProvider.setSelectedScreen(2); } }, child: Padding( From 4a2f7b8bdb8928e4d4edcf30d5599f26674cd39b Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 1 Nov 2023 21:14:45 +0100 Subject: [PATCH 060/177] Improved animation --- lib/screens/home/top_items/row_item.dart | 98 +++++++++++++++++------ lib/screens/home/top_items/top_items.dart | 1 + 2 files changed, 75 insertions(+), 24 deletions(-) diff --git a/lib/screens/home/top_items/row_item.dart b/lib/screens/home/top_items/row_item.dart index b22b33b..dbbd6d4 100644 --- a/lib/screens/home/top_items/row_item.dart +++ b/lib/screens/home/top_items/row_item.dart @@ -9,7 +9,7 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; -class RowItem extends StatelessWidget { +class RowItem extends StatefulWidget { final String type; final Color chartColor; final String domain; @@ -27,6 +27,53 @@ class RowItem extends StatelessWidget { required this.showColor, }) : super(key: key); + @override + State createState() => _RowItemState(); +} + +class _RowItemState extends State with TickerProviderStateMixin { + late AnimationController expandController; + late Animation animation; + + @override + void initState() { + super.initState(); + prepareAnimations(); + _runExpandCheck(); + } + + void prepareAnimations() { + expandController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 250) + ); + animation = CurvedAnimation( + parent: expandController, + curve: Curves.ease, + ); + } + + void _runExpandCheck() { + if (widget.showColor) { + expandController.forward(); + } + else { + expandController.reverse(); + } + } + + @override + void didUpdateWidget(oldWidget) { + super.didUpdateWidget(oldWidget); + _runExpandCheck(); + } + + @override + void dispose() { + expandController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final statusProvider = Provider.of(context); @@ -34,9 +81,9 @@ class RowItem extends StatelessWidget { final logsProvider = Provider.of(context); String? name; - if (clients == true) { + if (widget.clients == true) { try { - name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(domain)).name; + name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(widget.domain)).name; } catch (e) { // ---- // } @@ -45,30 +92,30 @@ class RowItem extends StatelessWidget { return Material( color: Colors.transparent, child: DomainOptions( - item: domain, - isClient: type == 'topClients', - isBlocked: type == 'topBlockedDomains', + item: widget.domain, + isClient: widget.type == 'topClients', + isBlocked: widget.type == 'topBlockedDomains', onTap: () { - if (type == 'topQueriedDomains' || type == 'topBlockedDomains') { - logsProvider.setSearchText(domain); + if (widget.type == 'topQueriedDomains' || widget.type == 'topBlockedDomains') { + logsProvider.setSearchText(widget.domain); logsProvider.setSelectedClients(null); logsProvider.setAppliedFilters( AppliedFiters( selectedResultStatus: 'all', - searchText: domain, + searchText: widget.domain, clients: null ) ); appConfigProvider.setSelectedScreen(2); } - else if (type == 'topClients') { + else if (widget.type == 'topClients') { logsProvider.setSearchText(null); - logsProvider.setSelectedClients([domain]); + logsProvider.setSelectedClients([widget.domain]); logsProvider.setAppliedFilters( AppliedFiters( selectedResultStatus: 'all', searchText: null, - clients: [domain] + clients: [widget.domain] ) ); appConfigProvider.setSelectedScreen(2); @@ -85,15 +132,18 @@ class RowItem extends StatelessWidget { Flexible( child: Row( children: [ - AnimatedContainer( - duration: const Duration(milliseconds: 200), - curve: Curves.ease, - margin: EdgeInsets.only(right: showColor ? 16 : 0), - width: showColor ? 12 : 0, - height: showColor ? 12 : 0, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - color: chartColor + SizeTransition( + axisAlignment: 1.0, + sizeFactor: animation, + axis: Axis.horizontal, + child: Container( + margin: const EdgeInsets.only(right: 16), + width: 12, + height: 12, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: widget.chartColor + ), ), ), Expanded( @@ -101,7 +151,7 @@ class RowItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - domain, + widget.domain, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 16, @@ -127,7 +177,7 @@ class RowItem extends StatelessWidget { ), const SizedBox(width: 16), Text( - number, + widget.number, style: TextStyle( color: Theme.of(context).colorScheme.onSurface ), @@ -168,7 +218,7 @@ class _OthersRowItemState extends State with SingleTickerProvider void prepareAnimations() { expandController = AnimationController( vsync: this, - duration: const Duration(milliseconds: 200) + duration: const Duration(milliseconds: 250) ); animation = CurvedAnimation( parent: expandController, diff --git a/lib/screens/home/top_items/top_items.dart b/lib/screens/home/top_items/top_items.dart index 97b61e0..5fd6435 100644 --- a/lib/screens/home/top_items/top_items.dart +++ b/lib/screens/home/top_items/top_items.dart @@ -153,6 +153,7 @@ class _TopItemsState extends State { expandedHeaderPadding: const EdgeInsets.all(0), elevation: 0, expansionCallback: (_, isExpanded) => setState(() => _showChart = isExpanded), + animationDuration: const Duration(milliseconds: 250), children: [ ExpansionPanel( headerBuilder: (context, isExpanded) => Padding( From 1ec4be5bc71c93317b6711893d8a612b51c3ee57 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 1 Nov 2023 21:31:19 +0100 Subject: [PATCH 061/177] Updated turkish translation --- lib/l10n/app_tr.arb | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index ef6edd7..27b066c 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -65,7 +65,7 @@ "theme": "Tema", "light": "Aydınlık", "dark": "Karanlık", - "systemDefined": "Sistem temasına uy", + "systemDefined": "Sistemle uyumlu olsun", "close": "Kapat", "connectedTo": "Bağlandı:", "selectedServer": "Seçili sunucu:", @@ -170,9 +170,9 @@ "search": "Ara", "dnsQueries": "DNS sorguları", "average": "Ortalama", - "blockedFilters": "Engellenen (Alan adları)", - "malwarePhishingBlocked": "Engellenen (Zararlı içerik)", - "blockedAdultWebsites": "Engellenen (Yetişkin içerik)", + "blockedFilters": "Engellenen alan adları", + "malwarePhishingBlocked": "Engellenen zararlı içerikler", + "blockedAdultWebsites": "Engellenen yetişkin içerikler", "generalSettings": "Genel ayarlar", "generalSettingsDescription": "Çeşitli farklı ayarlar", "hideZeroValues": "Sıfır değerlerini gizle", @@ -397,12 +397,12 @@ "loadingRewriteRules": "Yeniden yazma kuralları yükleniyor...", "rewriteRulesNotLoaded": "DNS yeniden yazma kuralları yüklenemedi.", "noRewriteRules": "DNS yeniden yazım kuralları yok", - "answer": "Cevap", - "deleteDnsRewrite": "DNS yeniden yazmayı sil", + "answer": "Yanıt", + "deleteDnsRewrite": "DNS yeniden yazımı sil", "deleteDnsRewriteMessage": "Bu DNS yeniden yazmasını silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", "dnsRewriteRuleDeleted": "DNS yeniden yazma kuralı başarıyla silindi", "dnsRewriteRuleNotDeleted": "DNS yeniden yazma kuralı silinemedi", - "addDnsRewrite": "DNS yeniden yazmayı ekle", + "addDnsRewrite": "DNS yeniden yazımı ekle", "addingRewrite": "Yeniden yazma ekleniyor...", "dnsRewriteRuleAdded": "DNS yeniden yazma kuralı başarıyla eklendi", "dnsRewriteRuleNotAdded": "DNS yeniden yazma kuralı eklenemedi", @@ -453,14 +453,14 @@ "limitRequestsSecond": "Saniye başına hız limiti", "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 et (DNSSEC etkinleştirilmiş bir çözümleyici gerekli).", + "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.", + "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.", + "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", @@ -501,7 +501,7 @@ "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 cevap verecektir.", + "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.", @@ -511,7 +511,7 @@ "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ı yolunu ayarla", + "certificateFilePath": "Sertifika dosyası belirle", "pasteCertificateContent": "Sertifika içeriğini yapıştır", "certificatePath": "Sertifika dosya yolu", "certificateContent": "Sertifika içeriği", @@ -560,7 +560,7 @@ "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 zaman", + "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ü", @@ -575,7 +575,7 @@ "completed": "Tamamlandı", "permissionNotGranted": "İzin verilmedi", "inputSearchTerm": "Bir arama terimi girin.", - "answers": "Cevaplar", + "answers": "Yanıtlar", "copyClipboard": "Panoya kopyala", "domainCopiedClipboard": "Alan adı panoya kopyalandı", "clearDnsCache": "DNS önbelleğini temizle", @@ -629,7 +629,7 @@ "ipLogsDescription": "Günlüklerde istemci adı yerine her zaman IP adresini göster", "application": "Uygulama", "combinedChart": "Birleştirilmiş grafik", - "combinedChartDescription": "Tüm grafikleri bir araya getirin", + "combinedChartDescription": "Tüm grafikleri bir araya getir", "statistics": "İstatistikler", "errorLoadFilters": "Filtreler yüklenirken hata oluştu.", "clientRemovedSuccessfully": "İstemci başarıyla kaldırıldı.", @@ -667,5 +667,7 @@ "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." + "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" } From f02406cf9d138331937ac776ff05984cc3ab51a7 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 2 Nov 2023 01:37:59 +0100 Subject: [PATCH 062/177] Theme changes --- lib/config/theme.dart | 94 ++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 59 deletions(-) diff --git a/lib/config/theme.dart b/lib/config/theme.dart index 7391677..a78a14a 100644 --- a/lib/config/theme.dart +++ b/lib/config/theme.dart @@ -1,64 +1,7 @@ import 'package:flutter/material.dart'; -ThemeData lightTheme(ColorScheme? dynamicColorScheme) => ThemeData( +final baseTheme = ThemeData( useMaterial3: true, - colorScheme: dynamicColorScheme, - snackBarTheme: SnackBarThemeData( - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5) - ), - elevation: 4, - ), - brightness: Brightness.light, listTileTheme: ListTileThemeData( - tileColor: Colors.transparent, - 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( - useMaterial3: true, - colorScheme: dynamicColorScheme, - snackBarTheme: SnackBarThemeData( - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5) - ), - elevation: 4, - ), - brightness: Brightness.dark, - listTileTheme: ListTileThemeData( - tileColor: Colors.transparent, - 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( - useMaterial3: true, - colorSchemeSeed: primaryColor, - snackBarTheme: SnackBarThemeData( - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5) - ), - elevation: 4, - ), - listTileTheme: const ListTileThemeData( - tileColor: Colors.transparent, - textColor: Color.fromRGBO(117, 117, 117, 1), - iconColor: Color.fromRGBO(117, 117, 117, 1), - ), - brightness: Brightness.light, - androidOverscrollIndicator: AndroidOverscrollIndicator.stretch -); - -ThemeData darkThemeOldVersions(MaterialColor primaryColor) => ThemeData( - useMaterial3: true, - colorSchemeSeed: primaryColor, snackBarTheme: SnackBarThemeData( contentTextStyle: const TextStyle( color: Colors.white @@ -69,11 +12,44 @@ ThemeData darkThemeOldVersions(MaterialColor primaryColor) => ThemeData( ), elevation: 4, ), +); + +ThemeData lightTheme(ColorScheme? dynamicColorScheme) => baseTheme.copyWith( + colorScheme: dynamicColorScheme, + brightness: Brightness.light, + listTileTheme: ListTileThemeData( + tileColor: Colors.transparent, + textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(117, 117, 117, 1), + iconColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(117, 117, 117, 1), + ), +); + +ThemeData darkTheme(ColorScheme? dynamicColorScheme) => baseTheme.copyWith( + colorScheme: dynamicColorScheme, + brightness: Brightness.dark, + listTileTheme: ListTileThemeData( + tileColor: Colors.transparent, + textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(187, 187, 187, 1), + iconColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(187, 187, 187, 1), + ), +); + +ThemeData lightThemeOldVersions(MaterialColor primaryColor) => baseTheme.copyWith( + colorScheme: ColorScheme.fromSeed(seedColor: primaryColor), + listTileTheme: const ListTileThemeData( + tileColor: Colors.transparent, + textColor: Color.fromRGBO(117, 117, 117, 1), + iconColor: Color.fromRGBO(117, 117, 117, 1), + ), + brightness: Brightness.light, +); + +ThemeData darkThemeOldVersions(MaterialColor primaryColor) => baseTheme.copyWith( + colorScheme: ColorScheme.fromSeed(seedColor: primaryColor), listTileTheme: const ListTileThemeData( tileColor: Colors.transparent, textColor: Color.fromRGBO(187, 187, 187, 1), iconColor: Color.fromRGBO(187, 187, 187, 1), ), brightness: Brightness.dark, - androidOverscrollIndicator: AndroidOverscrollIndicator.stretch ); \ No newline at end of file From 0e6cde57f00790ad8533243ca50115c549d8a917 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 2 Nov 2023 18:26:50 +0100 Subject: [PATCH 063/177] Revert theme changes --- lib/config/theme.dart | 94 +++++++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 35 deletions(-) diff --git a/lib/config/theme.dart b/lib/config/theme.dart index a78a14a..7391677 100644 --- a/lib/config/theme.dart +++ b/lib/config/theme.dart @@ -1,7 +1,64 @@ import 'package:flutter/material.dart'; -final baseTheme = ThemeData( +ThemeData lightTheme(ColorScheme? dynamicColorScheme) => ThemeData( useMaterial3: true, + colorScheme: dynamicColorScheme, + snackBarTheme: SnackBarThemeData( + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5) + ), + elevation: 4, + ), + brightness: Brightness.light, listTileTheme: ListTileThemeData( + tileColor: Colors.transparent, + 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( + useMaterial3: true, + colorScheme: dynamicColorScheme, + snackBarTheme: SnackBarThemeData( + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5) + ), + elevation: 4, + ), + brightness: Brightness.dark, + listTileTheme: ListTileThemeData( + tileColor: Colors.transparent, + 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( + useMaterial3: true, + colorSchemeSeed: primaryColor, + snackBarTheme: SnackBarThemeData( + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5) + ), + elevation: 4, + ), + listTileTheme: const ListTileThemeData( + tileColor: Colors.transparent, + textColor: Color.fromRGBO(117, 117, 117, 1), + iconColor: Color.fromRGBO(117, 117, 117, 1), + ), + brightness: Brightness.light, + androidOverscrollIndicator: AndroidOverscrollIndicator.stretch +); + +ThemeData darkThemeOldVersions(MaterialColor primaryColor) => ThemeData( + useMaterial3: true, + colorSchemeSeed: primaryColor, snackBarTheme: SnackBarThemeData( contentTextStyle: const TextStyle( color: Colors.white @@ -12,44 +69,11 @@ final baseTheme = ThemeData( ), elevation: 4, ), -); - -ThemeData lightTheme(ColorScheme? dynamicColorScheme) => baseTheme.copyWith( - colorScheme: dynamicColorScheme, - brightness: Brightness.light, - listTileTheme: ListTileThemeData( - tileColor: Colors.transparent, - textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(117, 117, 117, 1), - iconColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(117, 117, 117, 1), - ), -); - -ThemeData darkTheme(ColorScheme? dynamicColorScheme) => baseTheme.copyWith( - colorScheme: dynamicColorScheme, - brightness: Brightness.dark, - listTileTheme: ListTileThemeData( - tileColor: Colors.transparent, - textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(187, 187, 187, 1), - iconColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(187, 187, 187, 1), - ), -); - -ThemeData lightThemeOldVersions(MaterialColor primaryColor) => baseTheme.copyWith( - colorScheme: ColorScheme.fromSeed(seedColor: primaryColor), - listTileTheme: const ListTileThemeData( - tileColor: Colors.transparent, - textColor: Color.fromRGBO(117, 117, 117, 1), - iconColor: Color.fromRGBO(117, 117, 117, 1), - ), - brightness: Brightness.light, -); - -ThemeData darkThemeOldVersions(MaterialColor primaryColor) => baseTheme.copyWith( - colorScheme: ColorScheme.fromSeed(seedColor: primaryColor), listTileTheme: const ListTileThemeData( tileColor: Colors.transparent, textColor: Color.fromRGBO(187, 187, 187, 1), iconColor: Color.fromRGBO(187, 187, 187, 1), ), brightness: Brightness.dark, + androidOverscrollIndicator: AndroidOverscrollIndicator.stretch ); \ No newline at end of file From e5a971f3a3afadac3b199ccbe03845d663ad5568 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 2 Nov 2023 18:26:58 +0100 Subject: [PATCH 064/177] Bug fixes --- lib/main.dart | 2 +- lib/screens/clients/added_list.dart | 1 - .../clients/client/added_client_tile.dart | 8 +- .../filters/filters_triple_column.dart | 508 +++++++++--------- .../logs/configuration/config_widgets.dart | 16 +- .../logs/configuration/logs_config_modal.dart | 12 +- 6 files changed, 273 insertions(+), 274 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index b2bb30d..8696561 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -214,7 +214,7 @@ class _MainState extends State
with WidgetsBindingObserver { isBeta: appConfigProvider.getAppInfo!.version.contains('beta'), ); if (result != null && appConfigProvider.doNotRememberVersion != result.tagName && mounted) { - await showDialog( + showDialog( context: context, builder: (context) => UpdateModal( gitHubRelease: result, diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index eea9dd6..2c93a04 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -188,7 +188,6 @@ class _AddedListState extends State { : null, onDelete: openDeleteModal, splitView: widget.splitView, - serverVersion: statusProvider.serverStatus!.serverVersion, ), noData: SizedBox( width: double.maxFinite, diff --git a/lib/screens/clients/client/added_client_tile.dart b/lib/screens/clients/client/added_client_tile.dart index 9db4edb..aa3c31e 100644 --- a/lib/screens/clients/client/added_client_tile.dart +++ b/lib/screens/clients/client/added_client_tile.dart @@ -5,6 +5,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/functions/compare_versions.dart'; import 'package:adguard_home_manager/functions/copy_clipboard.dart'; import 'package:adguard_home_manager/models/clients.dart'; @@ -18,7 +19,6 @@ class AddedClientTile extends StatelessWidget { final void Function(Client) onDelete; final Client? selectedClient; final bool? splitView; - final String serverVersion; const AddedClientTile({ Key? key, @@ -29,11 +29,11 @@ class AddedClientTile extends StatelessWidget { required this.onDelete, this.selectedClient, required this.splitView, - required this.serverVersion }) : super(key: key); @override Widget build(BuildContext context) { + final statusProvider = Provider.of(context); final appConfigProvider = Provider.of(context); if (splitView == true) { @@ -147,7 +147,7 @@ class AddedClientTile extends StatelessWidget { Icons.search_rounded, size: 19, color: serverVersionIsAhead( - currentVersion: serverVersion, + currentVersion: statusProvider.serverStatus!.serverVersion, referenceVersion: 'v0.107.28', referenceVersionBeta: 'v0.108.0-b.33' ) == true @@ -261,7 +261,7 @@ class AddedClientTile extends StatelessWidget { Icons.search_rounded, size: 19, color: serverVersionIsAhead( - currentVersion: serverVersion, + currentVersion: statusProvider.serverStatus!.serverVersion, referenceVersion: 'v0.107.28', referenceVersionBeta: 'v0.108.0-b.33' ) == true diff --git a/lib/screens/filters/filters_triple_column.dart b/lib/screens/filters/filters_triple_column.dart index 3a8bfa2..6ecdd8f 100644 --- a/lib/screens/filters/filters_triple_column.dart +++ b/lib/screens/filters/filters_triple_column.dart @@ -73,258 +73,7 @@ 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), - 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(); - } - } + } return Scaffold( appBar: AppBar( @@ -334,7 +83,7 @@ class FiltersTripleColumn extends StatelessWidget { IconButton( onPressed: () async { final result = await filteringProvider.fetchFilters(); - if (result == false) { + if (result == false && context.mounted) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.errorLoadFilters, @@ -348,7 +97,258 @@ 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: 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), + 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(); + } + }, + ) ); } } \ No newline at end of file diff --git a/lib/screens/logs/configuration/config_widgets.dart b/lib/screens/logs/configuration/config_widgets.dart index 44523f3..2d70285 100644 --- a/lib/screens/logs/configuration/config_widgets.dart +++ b/lib/screens/logs/configuration/config_widgets.dart @@ -11,8 +11,8 @@ class LogsConfigOptions extends StatelessWidget { final bool anonymizeClientIp; final void Function(bool) updateAnonymizeClientIp; final List retentionItems; - final String? retentionTime; - final void Function(String?) updateRetentionTime; + final double? retentionTime; + final void Function(double?) updateRetentionTime; final void Function() onClear; final void Function() onConfirm; @@ -130,14 +130,12 @@ class LogsConfigOptions extends StatelessWidget { Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: DropdownButtonFormField( - items: retentionItems.map>((item) { - return DropdownMenuItem( - value: item.value.toString(), - child: Text(item.label), - ); - }).toList(), + items: retentionItems.map((item) => DropdownMenuItem( + value: item.value, + child: Text(item.label), + )).toList(), value: retentionTime, - onChanged: (value) => updateRetentionTime(value), + onChanged: (value) => updateRetentionTime(value as double), decoration: InputDecoration( border: const OutlineInputBorder( borderRadius: BorderRadius.all( diff --git a/lib/screens/logs/configuration/logs_config_modal.dart b/lib/screens/logs/configuration/logs_config_modal.dart index 698348c..8db61ba 100644 --- a/lib/screens/logs/configuration/logs_config_modal.dart +++ b/lib/screens/logs/configuration/logs_config_modal.dart @@ -79,7 +79,7 @@ class LogsConfigModalWidget extends StatefulWidget { class _LogsConfigModalWidgetState extends State { bool generalSwitch = false; bool anonymizeClientIp = false; - String? retentionTime = ""; + double? retentionTime; List retentionItems = []; @@ -101,8 +101,10 @@ class _LogsConfigModalWidgetState extends State { setState(() { generalSwitch = result['data']['enabled']; anonymizeClientIp = result['data']['anonymize_client_ip']; - retentionTime = result['data']['interval'].toString(); - loadStatus = LoadStatus.loading; + retentionTime = result['data']['interval'] != null + ? double.parse(result['data']['interval'].toString()) + : null; + loadStatus = LoadStatus.loaded; }); } else { @@ -191,7 +193,7 @@ class _LogsConfigModalWidgetState extends State { onClear: () => widget.onClear(), onConfirm: () => widget.onConfirm({ "enabled": generalSwitch, - "interval": double.parse(retentionTime!), + "interval": retentionTime, "anonymize_client_ip": anonymizeClientIp }) ); @@ -235,7 +237,7 @@ class _LogsConfigModalWidgetState extends State { onClear: () => widget.onClear(), onConfirm: () => widget.onConfirm({ "enabled": generalSwitch, - "interval": double.parse(retentionTime!), + "interval": retentionTime, "anonymize_client_ip": anonymizeClientIp }) ); From c1950fdc2bba5fcc286dc1c4492bc107aca47687 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 12 Nov 2023 18:51:41 +0100 Subject: [PATCH 065/177] Fixed update notification --- lib/main.dart | 26 +------------------------- lib/widgets/layout.dart | 31 ++++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 8696561..b025caa 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,9 +18,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/layout.dart'; import 'package:adguard_home_manager/widgets/menu_bar.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/widgets/update_modal.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; @@ -180,7 +177,7 @@ class Main extends StatefulWidget { State
createState() => _MainState(); } -class _MainState extends State
with WidgetsBindingObserver { +class _MainState extends State
{ List modes = []; DisplayMode? active; DisplayMode? preferred; @@ -201,28 +198,7 @@ class _MainState extends State
with WidgetsBindingObserver { void initState() { displayMode(); - WidgetsBinding.instance.addObserver(this); - super.initState(); - - WidgetsBinding.instance.addPostFrameCallback((_) async { - final appConfigProvider = Provider.of(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 && mounted) { - showDialog( - context: context, - builder: (context) => UpdateModal( - gitHubRelease: result, - onDownload: (link, version) => openUrl(link), - ), - ); - } - }); } @override diff --git a/lib/widgets/layout.dart b/lib/widgets/layout.dart index 8edcd56..8465a18 100644 --- a/lib/widgets/layout.dart +++ b/lib/widgets/layout.dart @@ -3,8 +3,11 @@ import 'package:animations/animations.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:adguard_home_manager/widgets/update_modal.dart'; import 'package:adguard_home_manager/widgets/system_ui_overlay_style.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/providers/app_config_provider.dart'; import 'package:adguard_home_manager/config/app_screens.dart'; import 'package:adguard_home_manager/config/sizes.dart'; @@ -19,13 +22,39 @@ class Layout extends StatefulWidget { State createState() => _LayoutState(); } -class _LayoutState extends State { +class _LayoutState extends State with WidgetsBindingObserver { bool _drawerExpanded = true; void _goBranch(int index) { Provider.of(context, listen: false).setSelectedScreen(index); } + @override + void initState() { + WidgetsBinding.instance.addObserver(this); + + super.initState(); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + final appConfigProvider = Provider.of(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 && mounted) { + await showDialog( + context: context, + builder: (context) => UpdateModal( + gitHubRelease: result, + onDownload: (link, version) => openUrl(link), + ), + ); + } + }); + } + @override Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; From 1ea71265e22522a2b673bbce6db1050bae1abb7c Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 15 Nov 2023 18:16:43 +0100 Subject: [PATCH 066/177] Bugs fixed --- lib/models/dhcp.dart | 2 +- lib/screens/filters/filters_list.dart | 2 +- .../access_settings/clients_list.dart | 2 +- lib/screens/settings/advanced_setings.dart | 1 + lib/services/http_requests.dart | 75 +++++++++++-------- lib/widgets/layout.dart | 8 +- 6 files changed, 54 insertions(+), 36 deletions(-) diff --git a/lib/models/dhcp.dart b/lib/models/dhcp.dart index c04ecc9..21b460b 100644 --- a/lib/models/dhcp.dart +++ b/lib/models/dhcp.dart @@ -76,7 +76,7 @@ class DhcpStatus { v6: json["v6"] != null ? IpVersion.fromJson(json["v6"]) : null, leases: List.from(json["leases"].map((x) => Lease.fromJson(x))), staticLeases: List.from(json["static_leases"].map((x) => Lease.fromJson(x))), - enabled: json["enabled"], + enabled: json["enabled"] ?? false, ); Map toJson() => { diff --git a/lib/screens/filters/filters_list.dart b/lib/screens/filters/filters_list.dart index 3fdf3d5..5c93fb0 100644 --- a/lib/screens/filters/filters_list.dart +++ b/lib/screens/filters/filters_list.dart @@ -132,7 +132,7 @@ class _FiltersListState extends State { TextButton.icon( onPressed: () async { final result = await filteringProvider.fetchFilters(); - if (result == false) { + if (result == false && mounted) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.errorLoadFilters, diff --git a/lib/screens/settings/access_settings/clients_list.dart b/lib/screens/settings/access_settings/clients_list.dart index 6f87c7c..fcffbef 100644 --- a/lib/screens/settings/access_settings/clients_list.dart +++ b/lib/screens/settings/access_settings/clients_list.dart @@ -70,7 +70,7 @@ class _ClientsListState extends State { Future refetchClients() async { final result = await clientsProvider.fetchClients(); - if (result == false) { + if (result == false && mounted) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientsNotLoaded, diff --git a/lib/screens/settings/advanced_setings.dart b/lib/screens/settings/advanced_setings.dart index 75572c1..39a93e3 100644 --- a/lib/screens/settings/advanced_setings.dart +++ b/lib/screens/settings/advanced_setings.dart @@ -24,6 +24,7 @@ class AdvancedSettings extends StatelessWidget { required Future Function(bool) function }) async { final result = await function(newStatus); + if (!context.mounted) return; if (result == true) { showSnacbkar( appConfigProvider: appConfigProvider, diff --git a/lib/services/http_requests.dart b/lib/services/http_requests.dart index 7fe18e0..a2eb5ec 100644 --- a/lib/services/http_requests.dart +++ b/lib/services/http_requests.dart @@ -1330,55 +1330,68 @@ class ApiClient { } Future getDhcpData() async { - final result = await Future.wait([ - apiRequest( - urlPath: '/dhcp/interfaces', - method: 'get', - server: server, - type: 'get_dhcp_data' - ), - apiRequest( - urlPath: '/dhcp/status', - method: 'get', - server: server, - type: 'get_dhcp_data' - ), - ]); + try { + final result = await Future.wait([ + apiRequest( + urlPath: '/dhcp/interfaces', + method: 'get', + server: server, + type: 'get_dhcp_data' + ), + apiRequest( + urlPath: '/dhcp/status', + method: 'get', + server: server, + type: 'get_dhcp_data' + ), + ]); - if (result[0]['hasResponse'] == true && result[1]['hasResponse'] == true) { - if (result[0]['statusCode'] == 200 && result[1]['statusCode'] == 200) { - List interfaces = List.from(jsonDecode(result[0]['body']).entries.map((entry) => NetworkInterface.fromJson(entry.value))); + if (result[0]['hasResponse'] == true && result[1]['hasResponse'] == true) { + if (result[0]['statusCode'] == 200 && result[1]['statusCode'] == 200) { + List interfaces = List.from(jsonDecode(result[0]['body']).entries.map((entry) => NetworkInterface.fromJson(entry.value))); - return { - 'result': 'success', - 'data': DhcpModel( - networkInterfaces: interfaces, - dhcpStatus: DhcpStatus.fromJson(jsonDecode(result[1]['body'])) - ) - }; + return { + 'result': 'success', + 'data': DhcpModel( + networkInterfaces: interfaces, + dhcpStatus: DhcpStatus.fromJson(jsonDecode(result[1]['body'])) + ) + }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'get_dhcp_data', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), + resBody: result.map((res) => res['body'] ?? 'null').toString(), + ) + }; + } } else { return { 'result': 'error', 'log': AppLog( - type: 'get_dhcp_data', + type: 'get_dhpc_data', dateTime: DateTime.now(), - message: 'error_code_not_expected', + message: [result[0]['log'].message, result[1]['log'].message].toString(), statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), resBody: result.map((res) => res['body'] ?? 'null').toString(), ) }; } - } - else { + } catch (e) { + Sentry.captureException(e); return { 'result': 'error', 'log': AppLog( type: 'get_dhpc_data', dateTime: DateTime.now(), - message: [result[0]['log'].message, result[1]['log'].message].toString(), - statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), - resBody: result.map((res) => res['body'] ?? 'null').toString(), + message: 'error_code_not_expected', + resBody: e.toString(), ) }; } diff --git a/lib/widgets/layout.dart b/lib/widgets/layout.dart index 8465a18..fe3aa1c 100644 --- a/lib/widgets/layout.dart +++ b/lib/widgets/layout.dart @@ -153,7 +153,9 @@ class _LayoutState extends State with WidgetsBindingObserver { child: child, ) ), - child: screens[appConfigProvider.selectedScreen].child, + child: appConfigProvider.selectedScreen < screens.length + ? screens[appConfigProvider.selectedScreen].child + : screens[0].child, ), ), ], @@ -177,7 +179,9 @@ class _LayoutState extends State with WidgetsBindingObserver { child: child, ) ), - child: screens[appConfigProvider.selectedScreen].child, + child: appConfigProvider.selectedScreen < screens.length + ? screens[appConfigProvider.selectedScreen].child + : screens[0].child, ), bottomNavigationBar: NavigationBar( selectedIndex: (serversProvider.selectedServer == null || serversProvider.apiClient == null) && appConfigProvider.selectedScreen > 1 From ad42438ce3737f9b5a5460be1f7069253ae5adac Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 15 Nov 2023 18:28:10 +0100 Subject: [PATCH 067/177] Changed list details modal opening --- lib/l10n/app_en.arb | 3 +- lib/l10n/app_es.arb | 3 +- lib/screens/filters/filters.dart | 47 ++++++------ lib/screens/filters/list_details_screen.dart | 79 +++++++++++--------- lib/screens/filters/list_options_menu.dart | 19 ++++- 5 files changed, 87 insertions(+), 64 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d4962e3..ab0908a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -669,5 +669,6 @@ "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" + "closeMenu": "Close menu", + "openListUrl": "Open list URL" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 2f202a8..9836280 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -669,5 +669,6 @@ "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ú" + "closeMenu": "Cerrar menú", + "openListUrl": "Abrir URL de lista" } \ No newline at end of file diff --git a/lib/screens/filters/filters.dart b/lib/screens/filters/filters.dart index 5975cf6..cb5cbdb 100644 --- a/lib/screens/filters/filters.dart +++ b/lib/screens/filters/filters.dart @@ -190,28 +190,31 @@ class _FiltersState extends State { } 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 actions() { diff --git a/lib/screens/filters/list_details_screen.dart b/lib/screens/filters/list_details_screen.dart index 236ee75..c0c9e2b 100644 --- a/lib/screens/filters/list_details_screen.dart +++ b/lib/screens/filters/list_details_screen.dart @@ -352,47 +352,52 @@ class _ListDetailsScreenState extends State { ); } 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: 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 + 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, + ), ), - child: Icon( - list.enabled == true - ? Icons.gpp_bad_rounded - : Icons.verified_user_rounded, - ), - ), - ) - ], + ) + ], + ), ), ); } diff --git a/lib/screens/filters/list_options_menu.dart b/lib/screens/filters/list_options_menu.dart index 1d644a0..91ca07f 100644 --- a/lib/screens/filters/list_options_menu.dart +++ b/lib/screens/filters/list_options_menu.dart @@ -1,6 +1,7 @@ // 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'; @@ -9,6 +10,8 @@ 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/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'; @@ -92,11 +95,16 @@ class ListOptionsMenu extends StatelessWidget { ); } ), + CustomListTile( + title: AppLocalizations.of(context)!.openListUrl, + icon: Icons.open_in_browser_rounded, + onTap: () => openUrl(list.url) + ), ], child: Material( color: Colors.transparent, child: InkWell( - onLongPress: () => showDialog( + onLongPress: Platform.isAndroid || Platform.isIOS ? () => showDialog( context: context, builder: (context) => OptionsModal( options: [ @@ -117,9 +125,14 @@ class ListOptionsMenu extends StatelessWidget { successMessage: AppLocalizations.of(context)!.listUrlCopied ) ), + MenuOption( + title: AppLocalizations.of(context)!.openListUrl, + icon: Icons.open_in_browser_rounded, + action: () => openUrl(list.url) + ), ] ) - ), + ) : null, child: child ), ), From 5768eac1b25b4f0368678db8e3c3ee9e8424c42a Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 15 Nov 2023 18:31:31 +0100 Subject: [PATCH 068/177] Open list url from modal --- lib/screens/filters/list_details_screen.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/screens/filters/list_details_screen.dart b/lib/screens/filters/list_details_screen.dart index c0c9e2b..90fb5c7 100644 --- a/lib/screens/filters/list_details_screen.dart +++ b/lib/screens/filters/list_details_screen.dart @@ -11,6 +11,7 @@ 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/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'; @@ -158,6 +159,11 @@ class _ListDetailsScreenState extends State { 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, From 9d3391aa0d76b0297e897618b0aa03e1a31f3b2a Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 15 Nov 2023 19:42:04 +0100 Subject: [PATCH 069/177] Added selection mode --- lib/l10n/app_en.arb | 3 +- lib/l10n/app_es.arb | 3 +- lib/screens/filters/list_options_menu.dart | 39 +++ lib/screens/filters/selection_screen.dart | 365 +++++++++++++++++++++ lib/widgets/custom_list_tile.dart | 4 +- 5 files changed, 411 insertions(+), 3 deletions(-) create mode 100644 lib/screens/filters/selection_screen.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ab0908a..9f955e9 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -670,5 +670,6 @@ "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" + "openListUrl": "Open list URL", + "selectionMode": "Selection mode" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 9836280..ab46506 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -670,5 +670,6 @@ "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" + "openListUrl": "Abrir URL de lista", + "selectionMode": "Modo de selección" } \ No newline at end of file diff --git a/lib/screens/filters/list_options_menu.dart b/lib/screens/filters/list_options_menu.dart index 91ca07f..698d803 100644 --- a/lib/screens/filters/list_options_menu.dart +++ b/lib/screens/filters/list_options_menu.dart @@ -9,6 +9,7 @@ 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/screens/filters/selection_screen.dart'; import 'package:adguard_home_manager/functions/open_url.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -36,6 +37,8 @@ class ListOptionsMenu extends StatelessWidget { final filteringProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + void enableDisable() async { ProcessModal processModal = ProcessModal(context: context); processModal.open( @@ -70,6 +73,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( @@ -100,6 +129,11 @@ class ListOptionsMenu extends StatelessWidget { icon: Icons.open_in_browser_rounded, onTap: () => openUrl(list.url) ), + CustomListTile( + title: AppLocalizations.of(context)!.selectionMode, + icon: Icons.check_rounded, + onTap: openSelectionMode + ), ], child: Material( color: Colors.transparent, @@ -130,6 +164,11 @@ class ListOptionsMenu extends StatelessWidget { icon: Icons.open_in_browser_rounded, action: () => openUrl(list.url) ), + MenuOption( + title: AppLocalizations.of(context)!.selectionMode, + icon: Icons.check_rounded, + action: openSelectionMode + ), ] ) ) : null, diff --git a/lib/screens/filters/selection_screen.dart b/lib/screens/filters/selection_screen.dart new file mode 100644 index 0000000..6ef3e77 --- /dev/null +++ b/lib/screens/filters/selection_screen.dart @@ -0,0 +1,365 @@ +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/custom_list_tile.dart'; + +import 'package:adguard_home_manager/models/filtering.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/providers/filtering_provider.dart'; + +class SelectionScreen extends StatefulWidget { + final bool isModal; + + const SelectionScreen({ + Key? key, + required this.isModal + }) : super(key: key); + + @override + State createState() => _SelectionScreenState(); +} + +class _SelectionScreenState extends State with TickerProviderStateMixin { + late TabController _tabController; + + List _selectedLists = []; + + @override + void initState() { + super.initState(); + _tabController = TabController( + initialIndex: 0, + length: 2, + vsync: this, + ); + } + + void handleSelect(Filter list) { + final isContained = _selectedLists.contains(list); + if (isContained) { + setState(() => _selectedLists = _selectedLists.where((l) => l != list).toList()); + } + else { + setState(() => _selectedLists.add(list)); + } + } + + @override + Widget build(BuildContext context) { + final filteringProvider = Provider.of(context); + + 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 + ), + ) + ], + ), + + ], + ), + ), + Expanded( + child: DefaultTabController( + length: 2, + child: Column( + children: [ + TabBar( + controller: _tabController, + unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, + tabs: [ + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.verified_user_rounded), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.whitelists,) + ], + ), + ), + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.gpp_bad_rounded), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.blacklists) + ], + ), + ), + ] + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: [ + _List( + lists: filteringProvider.filtering!.whitelistFilters, + selectedLists: _selectedLists, + onSelect: handleSelect, + ), + _List( + lists: filteringProvider.filtering!.filters, + selectedLists: _selectedLists, + onSelect: handleSelect, + ), + ] + ), + ) + ], + ) + ), + ) + ], + ), + ), + ); + } + else { + return Dialog.fullscreen( + child: 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( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.verified_user_rounded), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.whitelists,) + ], + ), + ), + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.gpp_bad_rounded), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.blacklists) + ], + ), + ), + ] + ) + ), + ) + ]; + }), + body: TabBarView( + controller: _tabController, + children: [ + _SliverList( + lists: filteringProvider.filtering!.whitelistFilters, + selectedLists: _selectedLists, + onSelect: handleSelect, + ), + _SliverList( + lists: filteringProvider.filtering!.filters, + selectedLists: _selectedLists, + onSelect: handleSelect, + ), + ] + ) + ) + ), + ); + } + } +} + +class _SliverList extends StatelessWidget { + final List lists; + final List selectedLists; + final void Function(Filter) onSelect; + + const _SliverList({ + Key? key, + required this.lists, + required this.selectedLists, + required this.onSelect, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final appConfigProvider = Provider.of(context); + + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (BuildContext context) { + return CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + if (lists.isNotEmpty) SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => CustomListTile( + title: lists[index].name, + subtitle: lists[index].url, + color: selectedLists.contains(lists[index]) + ? Theme.of(context).colorScheme.primaryContainer + : null, + trailing: Column( + children: [ + Icon( + lists[index].enabled == true + ? Icons.check_rounded + : Icons.close_rounded, + size: 12, + color: lists[index].enabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red + ), + const SizedBox(height: 4), + Text( + lists[index].enabled == true + ? AppLocalizations.of(context)!.enabled + : AppLocalizations.of(context)!.disabled, + style: TextStyle( + fontSize: 10, + color: lists[index].enabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red + ), + ) + ], + ), + onTap: () => onSelect(lists[index]), + ), + childCount: lists.length + ), + ), + if (lists.isEmpty) SliverFillRemaining( + child: Center( + child: Text( + AppLocalizations.of(context)!.noItems, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ) + ) + ) + ], + ); + }, + ), + ); + } +} + +class _List extends StatelessWidget { + final List lists; + final List selectedLists; + final void Function(Filter) onSelect; + + const _List({ + Key? key, + required this.lists, + required this.selectedLists, + required this.onSelect, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final appConfigProvider = Provider.of(context); + + return ListView.builder( + itemCount: lists.length, + itemBuilder: (context, index) => CustomListTile( + title: lists[index].name, + subtitle: lists[index].url, + color: selectedLists.contains(lists[index]) + ? Theme.of(context).colorScheme.primaryContainer + : null, + trailing: Column( + children: [ + Icon( + lists[index].enabled == true + ? Icons.check_rounded + : Icons.close_rounded, + size: 12, + color: lists[index].enabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red + ), + const SizedBox(height: 4), + Text( + lists[index].enabled == true + ? AppLocalizations.of(context)!.enabled + : AppLocalizations.of(context)!.disabled, + style: TextStyle( + fontSize: 10, + color: lists[index].enabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red + ), + ) + ], + ), + onTap: () => onSelect(lists[index]), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/custom_list_tile.dart b/lib/widgets/custom_list_tile.dart index 6f27129..0523468 100644 --- a/lib/widgets/custom_list_tile.dart +++ b/lib/widgets/custom_list_tile.dart @@ -11,6 +11,7 @@ class CustomListTile extends StatelessWidget { final void Function()? onLongPress; final bool? disabled; final void Function(bool)? onHover; + final Color? color; const CustomListTile({ Key? key, @@ -24,12 +25,13 @@ class CustomListTile extends StatelessWidget { this.onLongPress, this.disabled, this.onHover, + this.color, }) : super(key: key); @override Widget build(BuildContext context) { return Material( - color: Colors.transparent, + color: color ?? Colors.transparent, child: InkWell( onTap: onTap, onHover: onHover, From b6bf2d80c74c1fa95d439fbae43cee3b9c2b9a43 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 17 Nov 2023 01:30:48 +0100 Subject: [PATCH 070/177] Select items, select all and fabs --- lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/screens/filters/list_options_menu.dart | 2 +- .../filters/selection/selection_lists.dart | 215 +++++++++++ .../filters/selection/selection_screen.dart | 312 +++++++++++++++ lib/screens/filters/selection_screen.dart | 365 ------------------ 6 files changed, 534 insertions(+), 368 deletions(-) create mode 100644 lib/screens/filters/selection/selection_lists.dart create mode 100644 lib/screens/filters/selection/selection_screen.dart delete mode 100644 lib/screens/filters/selection_screen.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9f955e9..0d247be 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -671,5 +671,7 @@ "openMenu": "Open menu", "closeMenu": "Close menu", "openListUrl": "Open list URL", - "selectionMode": "Selection mode" + "selectionMode": "Selection mode", + "enableDisableSelected": "Enable or disable selected items", + "deleteSelected": "Delete selected items" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index ab46506..26be7c5 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -671,5 +671,7 @@ "openMenu": "Abrir menú", "closeMenu": "Cerrar menú", "openListUrl": "Abrir URL de lista", - "selectionMode": "Modo de selección" + "selectionMode": "Modo de selección", + "enableDisableSelected": "Activar o desactivar elementos seleccionados", + "deleteSelected": "Eliminar elementos seleccionados" } \ No newline at end of file diff --git a/lib/screens/filters/list_options_menu.dart b/lib/screens/filters/list_options_menu.dart index 698d803..0dbf2ab 100644 --- a/lib/screens/filters/list_options_menu.dart +++ b/lib/screens/filters/list_options_menu.dart @@ -9,7 +9,7 @@ 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/screens/filters/selection_screen.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'; diff --git a/lib/screens/filters/selection/selection_lists.dart b/lib/screens/filters/selection/selection_lists.dart new file mode 100644 index 0000000..7bb2a2c --- /dev/null +++ b/lib/screens/filters/selection/selection_lists.dart @@ -0,0 +1,215 @@ +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 lists; + final List 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 CheckboxListTile( + title: Text(AppLocalizations.of(context)!.selectAll), + value: lists.length == selectedLists.length, + onChanged: (value) { + if (value == true) { + selectAll(); + } + else { + unselectAll(); + } + }, + ); + } + return _Tile( + list: lists[index-1], + onSelect: onSelect, + isSelected: selectedLists.contains(lists[index-1]), + ); + } + ); + } +} + +class SelectionSliverList extends StatelessWidget { + final List lists; + final List selectedLists; + final void Function(Filter) onSelect; + final void Function() selectAll; + final void Function() unselectAll; + + const SelectionSliverList({ + 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 SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (BuildContext context) { + return CustomScrollView( + slivers: [ + 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({ + Key? key, + required this.list, + required this.onSelect, + required this.isSelected, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final appConfigProvider = Provider.of(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, + selectedColor: Theme.of(context).colorScheme.onSurface, + onTap: () => onSelect(list), + ); + } +} + diff --git a/lib/screens/filters/selection/selection_screen.dart b/lib/screens/filters/selection/selection_screen.dart new file mode 100644 index 0000000..375ae67 --- /dev/null +++ b/lib/screens/filters/selection/selection_screen.dart @@ -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/screens/filters/selection/selection_lists.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({ + Key? key, + required this.isModal + }) : super(key: key); + + @override + State createState() => _SelectionScreenState(); +} + +class _SelectionScreenState extends State with TickerProviderStateMixin { + late TabController _tabController; + late ScrollController _scrollController; + + bool _isScrolled = false; + + List _selectedWhitelists = []; + List _selectedBlacklists = []; + + @override + void initState() { + super.initState(); + _tabController = TabController( + initialIndex: 0, + length: 2, + vsync: this, + ); + _scrollController = ScrollController()..addListener(() { + setState(() => _isScrolled = _scrollController.offset > 0); + }); + } + + 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(context); + + final somethingSelected = _selectedBlacklists.isNotEmpty || _selectedWhitelists.isNotEmpty; + + void enableDisableSelected() { + + } + + void deleteSelected() { + + } + + 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 + ), + ) + ], + ), + + ], + ), + ), + 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)!.blacklist, + 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(() => []) + ), + 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( + controller: _scrollController, + 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)!.blacklist, + quantity: _selectedBlacklists.length + ), + ] + ) + ), + ) + ]; + }), + 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 = []), + ), + ] + ) + ) + ), + AnimatedPositioned( + duration: const Duration(milliseconds: 200), + curve: Curves.ease, + right: 16, + bottom: _isScrolled ? -150 : 16, + child: Column( + children: [ + FloatingActionButton.small( + onPressed: somethingSelected == true + ? () => enableDisableSelected() + : null, + tooltip: AppLocalizations.of(context)!.enableDisableSelected, + child: Icon( + Icons.shield_rounded, + color: somethingSelected == true + ? Theme.of(context).colorScheme.onPrimaryContainer + : Theme.of(context).colorScheme.secondary, + ), + ), + const SizedBox(height: 16), + FloatingActionButton.small( + onPressed: somethingSelected == true + ? () => deleteSelected() + : null, + tooltip: AppLocalizations.of(context)!.deleteSelected, + child: Icon( + Icons.delete_rounded, + color: somethingSelected == true + ? Theme.of(context).colorScheme.onPrimaryContainer + : Theme.of(context).colorScheme.secondary, + ), + ), + ], + ), + ) + ], + ), + ); + } + } +} + +class _Tab extends StatelessWidget { + final IconData icon; + final String text; + final int quantity; + + const _Tab({ + Key? key, + required this.icon, + required this.text, + required this.quantity + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon), + const SizedBox(width: 8), + Text(text), + const SizedBox(width: 8), + Container( + width: 18, + height: 18, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: Theme.of(context).colorScheme.primaryContainer + ), + child: Text( + quantity.toString(), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w700, + color: Theme.of(context).colorScheme.onPrimaryContainer + ), + ), + ) + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/filters/selection_screen.dart b/lib/screens/filters/selection_screen.dart deleted file mode 100644 index 6ef3e77..0000000 --- a/lib/screens/filters/selection_screen.dart +++ /dev/null @@ -1,365 +0,0 @@ -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/custom_list_tile.dart'; - -import 'package:adguard_home_manager/models/filtering.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; -import 'package:adguard_home_manager/providers/filtering_provider.dart'; - -class SelectionScreen extends StatefulWidget { - final bool isModal; - - const SelectionScreen({ - Key? key, - required this.isModal - }) : super(key: key); - - @override - State createState() => _SelectionScreenState(); -} - -class _SelectionScreenState extends State with TickerProviderStateMixin { - late TabController _tabController; - - List _selectedLists = []; - - @override - void initState() { - super.initState(); - _tabController = TabController( - initialIndex: 0, - length: 2, - vsync: this, - ); - } - - void handleSelect(Filter list) { - final isContained = _selectedLists.contains(list); - if (isContained) { - setState(() => _selectedLists = _selectedLists.where((l) => l != list).toList()); - } - else { - setState(() => _selectedLists.add(list)); - } - } - - @override - Widget build(BuildContext context) { - final filteringProvider = Provider.of(context); - - 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 - ), - ) - ], - ), - - ], - ), - ), - Expanded( - child: DefaultTabController( - length: 2, - child: Column( - children: [ - TabBar( - controller: _tabController, - unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, - tabs: [ - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.verified_user_rounded), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.whitelists,) - ], - ), - ), - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.gpp_bad_rounded), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.blacklists) - ], - ), - ), - ] - ), - Expanded( - child: TabBarView( - controller: _tabController, - children: [ - _List( - lists: filteringProvider.filtering!.whitelistFilters, - selectedLists: _selectedLists, - onSelect: handleSelect, - ), - _List( - lists: filteringProvider.filtering!.filters, - selectedLists: _selectedLists, - onSelect: handleSelect, - ), - ] - ), - ) - ], - ) - ), - ) - ], - ), - ), - ); - } - else { - return Dialog.fullscreen( - child: 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( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.verified_user_rounded), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.whitelists,) - ], - ), - ), - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.gpp_bad_rounded), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.blacklists) - ], - ), - ), - ] - ) - ), - ) - ]; - }), - body: TabBarView( - controller: _tabController, - children: [ - _SliverList( - lists: filteringProvider.filtering!.whitelistFilters, - selectedLists: _selectedLists, - onSelect: handleSelect, - ), - _SliverList( - lists: filteringProvider.filtering!.filters, - selectedLists: _selectedLists, - onSelect: handleSelect, - ), - ] - ) - ) - ), - ); - } - } -} - -class _SliverList extends StatelessWidget { - final List lists; - final List selectedLists; - final void Function(Filter) onSelect; - - const _SliverList({ - Key? key, - required this.lists, - required this.selectedLists, - required this.onSelect, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final appConfigProvider = Provider.of(context); - - return SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (BuildContext context) { - return CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - if (lists.isNotEmpty) SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) => CustomListTile( - title: lists[index].name, - subtitle: lists[index].url, - color: selectedLists.contains(lists[index]) - ? Theme.of(context).colorScheme.primaryContainer - : null, - trailing: Column( - children: [ - Icon( - lists[index].enabled == true - ? Icons.check_rounded - : Icons.close_rounded, - size: 12, - color: lists[index].enabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red - ), - const SizedBox(height: 4), - Text( - lists[index].enabled == true - ? AppLocalizations.of(context)!.enabled - : AppLocalizations.of(context)!.disabled, - style: TextStyle( - fontSize: 10, - color: lists[index].enabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red - ), - ) - ], - ), - onTap: () => onSelect(lists[index]), - ), - childCount: lists.length - ), - ), - if (lists.isEmpty) SliverFillRemaining( - child: Center( - child: Text( - AppLocalizations.of(context)!.noItems, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ) - ) - ) - ], - ); - }, - ), - ); - } -} - -class _List extends StatelessWidget { - final List lists; - final List selectedLists; - final void Function(Filter) onSelect; - - const _List({ - Key? key, - required this.lists, - required this.selectedLists, - required this.onSelect, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final appConfigProvider = Provider.of(context); - - return ListView.builder( - itemCount: lists.length, - itemBuilder: (context, index) => CustomListTile( - title: lists[index].name, - subtitle: lists[index].url, - color: selectedLists.contains(lists[index]) - ? Theme.of(context).colorScheme.primaryContainer - : null, - trailing: Column( - children: [ - Icon( - lists[index].enabled == true - ? Icons.check_rounded - : Icons.close_rounded, - size: 12, - color: lists[index].enabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red - ), - const SizedBox(height: 4), - Text( - lists[index].enabled == true - ? AppLocalizations.of(context)!.enabled - : AppLocalizations.of(context)!.disabled, - style: TextStyle( - fontSize: 10, - color: lists[index].enabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red - ), - ) - ], - ), - onTap: () => onSelect(lists[index]), - ), - ); - } -} \ No newline at end of file From b8b34f7f47dcfb172f637528c0e2b215da2966aa Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 18 Nov 2023 22:58:27 +0100 Subject: [PATCH 071/177] Added enable disable lists and delete lists --- lib/l10n/app_en.arb | 10 +- lib/l10n/app_es.arb | 10 +- lib/models/filtering.dart | 16 +- lib/providers/filtering_provider.dart | 71 +++++++ .../selection/delete_selection_modal.dart | 150 ++++++++++++++ .../enable_disable_selection_modal.dart | 193 ++++++++++++++++++ .../filters/selection/selection_lists.dart | 125 ++++++++++-- .../selection/selection_result_modal.dart | 102 +++++++++ .../filters/selection/selection_screen.dart | 86 +++++++- lib/widgets/system_ui_overlay_style.dart | 2 +- 10 files changed, 739 insertions(+), 26 deletions(-) create mode 100644 lib/screens/filters/selection/delete_selection_modal.dart create mode 100644 lib/screens/filters/selection/enable_disable_selection_modal.dart create mode 100644 lib/screens/filters/selection/selection_result_modal.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 0d247be..2fc732c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -673,5 +673,13 @@ "openListUrl": "Open list URL", "selectionMode": "Selection mode", "enableDisableSelected": "Enable or disable selected items", - "deleteSelected": "Delete 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" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 26be7c5..6f2d8d6 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -673,5 +673,13 @@ "openListUrl": "Abrir URL de lista", "selectionMode": "Modo de selección", "enableDisableSelected": "Activar o desactivar elementos seleccionados", - "deleteSelected": "Eliminar 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." } \ No newline at end of file diff --git a/lib/models/filtering.dart b/lib/models/filtering.dart index 9b1d6ce..7b521d2 100644 --- a/lib/models/filtering.dart +++ b/lib/models/filtering.dart @@ -1,6 +1,6 @@ class Filtering { - final List filters; - final List whitelistFilters; + List filters; + List whitelistFilters; List userRules; List 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 + }); +} \ No newline at end of file diff --git a/lib/providers/filtering_provider.dart b/lib/providers/filtering_provider.dart index d416455..75cfbed 100644 --- a/lib/providers/filtering_provider.dart +++ b/lib/providers/filtering_provider.dart @@ -384,4 +384,75 @@ class FilteringProvider with ChangeNotifier { return false; } } + + Future> deleteMultipleLists({ + required List blacklists, + required List whitelists + }) async { + Future deleteList({ + required Filter list, + required bool isWhitelist, + }) async { + final result = await _serversProvider!.apiClient!.deleteFilterList( + data: { + "url": list.url, + "whitelist": isWhitelist + } + ); + if (result['result'] == 'success') { + 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> enableDisableMultipleLists({ + required List blacklists, + required List whitelists + }) async { + Future enableDisableList({ + required Filter list, + required bool isWhitelist, + }) async { + final result = await _serversProvider!.apiClient!.updateFilterList( + data: { + "data": { + "enabled": !list.enabled, + "name": list.name, + "url": list.url + }, + "url": list.url, + "whitelist": isWhitelist + } + ); + if (result['result'] == 'success') { + 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, + ]; + } } \ No newline at end of file diff --git a/lib/screens/filters/selection/delete_selection_modal.dart b/lib/screens/filters/selection/delete_selection_modal.dart new file mode 100644 index 0000000..944d515 --- /dev/null +++ b/lib/screens/filters/selection/delete_selection_modal.dart @@ -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 selectedWhitelists; + final List selectedBlacklists; + final void Function() onDelete; + + const DeleteSelectionModal({ + Key? key, + required this.selectedBlacklists, + required this.selectedWhitelists, + required this.onDelete, + }) : super(key: key); + + @override + State createState() => _DeleteSelectionModalState(); +} + +class _DeleteSelectionModalState extends State { + 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) + ), + ], + ) + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/filters/selection/enable_disable_selection_modal.dart b/lib/screens/filters/selection/enable_disable_selection_modal.dart new file mode 100644 index 0000000..08a9b3d --- /dev/null +++ b/lib/screens/filters/selection/enable_disable_selection_modal.dart @@ -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 selectedWhitelists; + final List selectedBlacklists; + final void Function() onDelete; + + const EnableDisableSelectionModal({ + Key? key, + required this.selectedBlacklists, + required this.selectedWhitelists, + required this.onDelete, + }) : super(key: key); + + @override + State createState() => _EnableDisableSelectionModalState(); +} + +class _EnableDisableSelectionModalState extends State { + 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)!.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) + ), + ], + ) + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/filters/selection/selection_lists.dart b/lib/screens/filters/selection/selection_lists.dart index 7bb2a2c..1525e24 100644 --- a/lib/screens/filters/selection/selection_lists.dart +++ b/lib/screens/filters/selection/selection_lists.dart @@ -27,20 +27,34 @@ class SelectionList extends StatelessWidget { itemCount: lists.length+1, itemBuilder: (context, index) { if (index == 0) { - return CheckboxListTile( - title: Text(AppLocalizations.of(context)!.selectAll), - value: lists.length == selectedLists.length, - onChanged: (value) { - if (value == true) { - selectAll(); - } - else { - unselectAll(); - } - }, + 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 _Tile( + return _CheckboxTile( list: lists[index-1], onSelect: onSelect, isSelected: selectedLists.contains(lists[index-1]), @@ -206,10 +220,95 @@ class _Tile extends StatelessWidget { horizontal: 16 ), selected: isSelected, - selectedTileColor: Theme.of(context).colorScheme.primaryContainer, + 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(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), + ); + } +} + diff --git a/lib/screens/filters/selection/selection_result_modal.dart b/lib/screens/filters/selection/selection_result_modal.dart new file mode 100644 index 0000000..3a343d3 --- /dev/null +++ b/lib/screens/filters/selection/selection_result_modal.dart @@ -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 results; + final void Function() onClose; + final SelectionResultMode mode; + + const SelectionResultModal({ + Key? key, + required this.results, + required this.onClose, + required this.mode, + }) : super(key: key); + + @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.shield_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) + ), + ], + ) + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/filters/selection/selection_screen.dart b/lib/screens/filters/selection/selection_screen.dart index 375ae67..8ca8565 100644 --- a/lib/screens/filters/selection/selection_screen.dart +++ b/lib/screens/filters/selection/selection_screen.dart @@ -2,8 +2,12 @@ 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'; @@ -39,7 +43,7 @@ class _SelectionScreenState extends State with TickerProviderSt vsync: this, ); _scrollController = ScrollController()..addListener(() { - setState(() => _isScrolled = _scrollController.offset > 0); + setState(() => _isScrolled = _scrollController.offset > 20); }); } @@ -71,11 +75,65 @@ class _SelectionScreenState extends State with TickerProviderSt 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(context: context); + 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(context: context); + 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) { @@ -107,7 +165,21 @@ class _SelectionScreenState extends State with TickerProviderSt ) ], ), - + Row( + children: [ + IconButton( + onPressed: enableDisableSelected, + icon: const Icon(Icons.shield_rounded), + tooltip: AppLocalizations.of(context)!.enableDisableSelected, + ), + const SizedBox(width: 8), + IconButton( + onPressed: deleteSelected, + icon: const Icon(Icons.delete_rounded), + tooltip: AppLocalizations.of(context)!.deleteSelectedLists, + ), + ], + ) ], ), ), @@ -141,7 +213,7 @@ class _SelectionScreenState extends State with TickerProviderSt selectedLists: _selectedWhitelists, onSelect: (list) => handleSelect(list, ListType.whitelist), selectAll: () => setState(() => _selectedWhitelists = filteringProvider.filtering!.whitelistFilters), - unselectAll: () => setState(() => []) + unselectAll: () => setState(() => _selectedWhitelists = []) ), SelectionList( lists: filteringProvider.filtering!.filters, @@ -240,7 +312,7 @@ class _SelectionScreenState extends State with TickerProviderSt Icons.shield_rounded, color: somethingSelected == true ? Theme.of(context).colorScheme.onPrimaryContainer - : Theme.of(context).colorScheme.secondary, + : Theme.of(context).colorScheme.onPrimaryContainer.withOpacity(0.5), ), ), const SizedBox(height: 16), @@ -253,7 +325,7 @@ class _SelectionScreenState extends State with TickerProviderSt Icons.delete_rounded, color: somethingSelected == true ? Theme.of(context).colorScheme.onPrimaryContainer - : Theme.of(context).colorScheme.secondary, + : Theme.of(context).colorScheme.onPrimaryContainer.withOpacity(0.5), ), ), ], diff --git a/lib/widgets/system_ui_overlay_style.dart b/lib/widgets/system_ui_overlay_style.dart index 8ceaf4b..83557f1 100644 --- a/lib/widgets/system_ui_overlay_style.dart +++ b/lib/widgets/system_ui_overlay_style.dart @@ -20,7 +20,7 @@ class OverlayStyle extends StatelessWidget { statusBarIconBrightness: Theme.of(context).brightness == Brightness.light ? Brightness.dark : Brightness.light, - systemNavigationBarColor: Theme.of(context).scaffoldBackgroundColor, + systemNavigationBarColor: Theme.of(context).colorScheme.background, systemNavigationBarIconBrightness: Theme.of(context).brightness == Brightness.light ? Brightness.dark : Brightness.light, From a19612575bb569c7df8239db54c1601c2fd9615d Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 18 Nov 2023 23:00:00 +0100 Subject: [PATCH 072/177] Auto close context menu --- lib/screens/filters/list_options_menu.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/screens/filters/list_options_menu.dart b/lib/screens/filters/list_options_menu.dart index 0dbf2ab..e2521a3 100644 --- a/lib/screens/filters/list_options_menu.dart +++ b/lib/screens/filters/list_options_menu.dart @@ -127,12 +127,18 @@ class ListOptionsMenu extends StatelessWidget { CustomListTile( title: AppLocalizations.of(context)!.openListUrl, icon: Icons.open_in_browser_rounded, - onTap: () => openUrl(list.url) + onTap: () { + Navigator.pop(context); // Closes the context menu + openUrl(list.url); + } ), CustomListTile( title: AppLocalizations.of(context)!.selectionMode, icon: Icons.check_rounded, - onTap: openSelectionMode + onTap: () { + Navigator.pop(context); // Closes the context menu + openSelectionMode(); + } ), ], child: Material( From 0a06ee6118d274d4843b261f727c9845227cb5ad Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 18 Nov 2023 23:06:46 +0100 Subject: [PATCH 073/177] Change location files filters screen --- lib/screens/filters/add_button.dart | 4 ++-- lib/screens/filters/{ => details}/add_list_modal.dart | 0 .../filters/{ => details}/check_host_modal.dart | 0 .../filters/{ => details}/list_details_screen.dart | 4 ++-- lib/screens/filters/filters.dart | 10 +++++----- lib/screens/filters/{ => modals}/add_custom_rule.dart | 0 .../filters/{ => modals}/blocked_services_screen.dart | 0 .../filters/{ => modals}/delete_list_modal.dart | 0 .../filters/{ => modals}/remove_custom_rule_modal.dart | 0 .../{ => modals}/update_interval_lists_modal.dart | 0 10 files changed, 9 insertions(+), 9 deletions(-) rename lib/screens/filters/{ => details}/add_list_modal.dart (100%) rename lib/screens/filters/{ => details}/check_host_modal.dart (100%) rename lib/screens/filters/{ => details}/list_details_screen.dart (98%) rename lib/screens/filters/{ => modals}/add_custom_rule.dart (100%) rename lib/screens/filters/{ => modals}/blocked_services_screen.dart (100%) rename lib/screens/filters/{ => modals}/delete_list_modal.dart (100%) rename lib/screens/filters/{ => modals}/remove_custom_rule_modal.dart (100%) rename lib/screens/filters/{ => modals}/update_interval_lists_modal.dart (100%) diff --git a/lib/screens/filters/add_button.dart b/lib/screens/filters/add_button.dart index e96bb51..fbab84c 100644 --- a/lib/screens/filters/add_button.dart +++ b/lib/screens/filters/add_button.dart @@ -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'; diff --git a/lib/screens/filters/add_list_modal.dart b/lib/screens/filters/details/add_list_modal.dart similarity index 100% rename from lib/screens/filters/add_list_modal.dart rename to lib/screens/filters/details/add_list_modal.dart diff --git a/lib/screens/filters/check_host_modal.dart b/lib/screens/filters/details/check_host_modal.dart similarity index 100% rename from lib/screens/filters/check_host_modal.dart rename to lib/screens/filters/details/check_host_modal.dart diff --git a/lib/screens/filters/list_details_screen.dart b/lib/screens/filters/details/list_details_screen.dart similarity index 98% rename from lib/screens/filters/list_details_screen.dart rename to lib/screens/filters/details/list_details_screen.dart index 90fb5c7..8c8199a 100644 --- a/lib/screens/filters/list_details_screen.dart +++ b/lib/screens/filters/details/list_details_screen.dart @@ -7,8 +7,8 @@ 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'; diff --git a/lib/screens/filters/filters.dart b/lib/screens/filters/filters.dart index cb5cbdb..6d811a2 100644 --- a/lib/screens/filters/filters.dart +++ b/lib/screens/filters/filters.dart @@ -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'; diff --git a/lib/screens/filters/add_custom_rule.dart b/lib/screens/filters/modals/add_custom_rule.dart similarity index 100% rename from lib/screens/filters/add_custom_rule.dart rename to lib/screens/filters/modals/add_custom_rule.dart diff --git a/lib/screens/filters/blocked_services_screen.dart b/lib/screens/filters/modals/blocked_services_screen.dart similarity index 100% rename from lib/screens/filters/blocked_services_screen.dart rename to lib/screens/filters/modals/blocked_services_screen.dart diff --git a/lib/screens/filters/delete_list_modal.dart b/lib/screens/filters/modals/delete_list_modal.dart similarity index 100% rename from lib/screens/filters/delete_list_modal.dart rename to lib/screens/filters/modals/delete_list_modal.dart diff --git a/lib/screens/filters/remove_custom_rule_modal.dart b/lib/screens/filters/modals/remove_custom_rule_modal.dart similarity index 100% rename from lib/screens/filters/remove_custom_rule_modal.dart rename to lib/screens/filters/modals/remove_custom_rule_modal.dart diff --git a/lib/screens/filters/update_interval_lists_modal.dart b/lib/screens/filters/modals/update_interval_lists_modal.dart similarity index 100% rename from lib/screens/filters/update_interval_lists_modal.dart rename to lib/screens/filters/modals/update_interval_lists_modal.dart From 6ecc4324bb1f53cd977f7ab9f7ac08abb9b6970a Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 18 Nov 2023 23:14:44 +0100 Subject: [PATCH 074/177] Removed androidOverscrollIndicator property --- lib/config/theme.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/config/theme.dart b/lib/config/theme.dart index 7391677..8a8aadf 100644 --- a/lib/config/theme.dart +++ b/lib/config/theme.dart @@ -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 ); \ No newline at end of file From 0e4f4c8eff056b5aa08632d9cab5b3988954f761 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 18 Nov 2023 23:31:14 +0100 Subject: [PATCH 075/177] Updated libraries and flutter version --- README.md | 3 +- android/app/src/main/AndroidManifest.xml | 3 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- macos/Podfile.lock | 32 +-- pubspec.lock | 202 ++++++++++-------- pubspec.yaml | 43 ++-- 6 files changed, 150 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index 4128ac3..0a4af9d 100644 --- a/README.md +++ b/README.md @@ -121,8 +121,7 @@ On [this repository](https://github.com/JuanRodenas/Pihole_list) you can find a - [flutter dotenv](https://pub.dev/packages/flutter_dotenv) - [flutter reorderable list](https://pub.dev/packages/flutter_reorderable_list) - [pie chart](https://pub.dev/packages/pie_chart) -- [go router](https://pub.dev/packages/go_router) -- [flutter hooks](https://pub.dev/packages/flutter_hooks) +- [segmented button slide](https://pub.dev/packages/segmented_button_slide)
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index dbe31f9..603fecc 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -15,7 +15,8 @@ android:label="AdGuard Home Manager" android:name="${applicationName}" android:requestLegacyExternalStorage="true" - android:icon="@mipmap/ic_launcher"> + android:icon="@mipmap/ic_launcher" + android:enableOnBackInvokedCallback="true"> = 2.7.5) - - sqlite3 (3.43.0): - - sqlite3/common (= 3.43.0) - - sqlite3/common (3.43.0) - - sqlite3/fts5 (3.43.0): + - sqlite3 (3.44.0): + - sqlite3/common (= 3.44.0) + - sqlite3/common (3.44.0) + - sqlite3/fts5 (3.44.0): - sqlite3/common - - sqlite3/perf-threadsafe (3.43.0): + - sqlite3/perf-threadsafe (3.44.0): - sqlite3/common - - sqlite3/rtree (3.43.0): + - sqlite3/rtree (3.44.0): - sqlite3/common - sqlite3_flutter_libs (0.0.1): - FlutterMacOS - - sqlite3 (~> 3.43.0) + - sqlite3 (~> 3.44.0) - sqlite3/fts5 - sqlite3/perf-threadsafe - sqlite3/rtree @@ -83,12 +83,12 @@ SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce - Sentry: e3203780941722a1fcfee99e351de14244c7f806 - sentry_flutter: 8f0ffd53088e6a4d50c095852c5cad9e4405025c - SentryPrivate: 5e3683390f66611fc7c6215e27645873adb55d13 + Sentry: 6f5742b4c47c17c9adcf265f6f328cf4a0ed1923 + sentry_flutter: 2c309a1d4b45e59d02cfa15795705687f1e2081b + SentryPrivate: b2f7996f37781080f04a946eb4e377ff63c64195 sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea - sqlite3: 7afcf055d3700254769a4dcba56f27d26b5515c9 - sqlite3_flutter_libs: 03613b0558ba0bb5544aa3ba3d0862c09c7a19b3 + sqlite3: 6e2d4a4879854d0ec86b476bf3c3e30870bac273 + sqlite3_flutter_libs: 5b7e226d522d67be60d7ade93f5aa11ebc0cd796 url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 window_size: 339dafa0b27a95a62a843042038fa6c3c48de195 diff --git a/pubspec.lock b/pubspec.lock index 0741c5f..b32374c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: archive - sha256: "49b1fad315e57ab0bbc15bcbb874e83116a1d78f77ebd500a4af6c9407d6b28e" + sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b" url: "https://pub.dev" source: hosted - version: "3.3.8" + version: "3.4.9" args: dependency: transitive description: @@ -85,10 +85,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" contextmenu: dependency: "direct main" description: @@ -133,10 +133,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "86add5ef97215562d2e090535b0a16f197902b10c369c558a100e74ea06e8659" + sha256: "7035152271ff67b072a211152846e9f1259cf1be41e34cd3e0b5463d2d6b8419" url: "https://pub.dev" source: hosted - version: "9.0.3" + version: "9.1.0" device_info_plus_platform_interface: dependency: transitive description: @@ -149,10 +149,10 @@ packages: dependency: "direct main" description: name: dynamic_color - sha256: de4798a7069121aee12d5895315680258415de9b00e717723a1bd73d58f0126d + sha256: "8b8bd1d798bd393e11eddeaa8ae95b12ff028bf7d5998fc5d003488cd5f4ce2f" url: "https://pub.dev" source: hosted - version: "1.6.6" + version: "1.6.8" equatable: dependency: transitive description: @@ -197,10 +197,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "48a1b69be9544e2b03d9a8e843affd89e43f3194c9248776222efcb4206bb1ec" + sha256: "6b9eb2b3017241d05c482c01f668dd05cc909ec9a0114fdd49acd958ff2432fa" url: "https://pub.dev" source: hosted - version: "0.62.0" + version: "0.64.0" flutter: dependency: "direct main" description: flutter @@ -222,14 +222,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" - flutter_hooks: - dependency: "direct main" - description: - name: flutter_hooks - sha256: "7c8db779c2d1010aa7f9ea3fbefe8f86524fcb87b69e8b0af31e1a4b55422dec" - url: "https://pub.dev" - source: hosted - version: "0.20.3" flutter_html: dependency: "direct main" description: @@ -250,10 +242,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "3.0.1" flutter_localizations: dependency: "direct main" description: flutter @@ -263,18 +255,18 @@ packages: dependency: "direct main" description: name: flutter_markdown - sha256: d4a1cb250c4e059586af0235f32e02882860a508e189b61f2b31b8810c1e1330 + sha256: "35108526a233cc0755664d445f8a6b4b61e6f8fe993b3658b80b4a26827fc196" url: "https://pub.dev" source: hosted - version: "0.6.17+2" + version: "0.6.18+2" flutter_native_splash: dependency: "direct dev" description: name: flutter_native_splash - sha256: ecff62b3b893f2f665de7e4ad3de89f738941fcfcaaba8ee601e749efafa4698 + sha256: d93394f22f73e810bda59e11ebe83329c5511d6460b6b7509c4e1f3c92d6d625 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.5" flutter_reorderable_list: dependency: "direct main" description: @@ -296,10 +288,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "6ff9fa12892ae074092de2fa6a9938fb21dbabfdaa2ff57dc697ff912fc8d4b2" + sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c url: "https://pub.dev" source: hosted - version: "1.1.6" + version: "2.0.9" flutter_test: dependency: "direct dev" description: flutter @@ -346,10 +338,10 @@ packages: dependency: transitive description: name: image - sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf + sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271" url: "https://pub.dev" source: hosted - version: "4.0.17" + version: "4.1.3" intl: dependency: "direct main" description: @@ -378,10 +370,10 @@ packages: dependency: transitive description: name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "3.0.0" list_counter: dependency: transitive description: @@ -418,10 +410,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" nested: dependency: transitive description: @@ -434,10 +426,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "6ff267fcd9d48cb61c8df74a82680e8b82e940231bb5f68356672fde0397334a" + sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.0" package_info_plus_platform_interface: dependency: transitive description: @@ -454,14 +446,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" - path_drawing: - dependency: transitive - description: - name: path_drawing - sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 - url: "https://pub.dev" - source: hosted - version: "1.0.1" path_parsing: dependency: transitive description: @@ -482,26 +466,26 @@ packages: dependency: transitive description: name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "6.0.1" pie_chart: dependency: "direct main" description: name: pie_chart - sha256: "5dba6d0eb4718e8ed00a9079361cd8947c3f84ac5a5d76f05a27f4ec5e27589e" + sha256: "58e6a46999ac938bfa1c3e5be414d6e149f037647197dca03ba3614324c12c82" url: "https://pub.dev" source: hosted - version: "5.3.2" + version: "5.4.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.7" pointycastle: dependency: transitive description: @@ -514,10 +498,10 @@ packages: dependency: "direct main" description: name: provider - sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" url: "https://pub.dev" source: hosted - version: "6.0.5" + version: "6.1.1" segmented_button_slide: dependency: "direct main" description: @@ -530,18 +514,18 @@ packages: dependency: transitive description: name: sentry - sha256: "39c23342fc96105da449914f7774139a17a0ca8a4e70d9ad5200171f7e47d6ba" + sha256: e8040183ea1a0323999bce69786ed8429b1b89fbe5815a504d5bb7493a6464cc url: "https://pub.dev" source: hosted - version: "7.9.0" + version: "7.13.1" sentry_flutter: dependency: "direct main" description: name: sentry_flutter - sha256: ff68ab31918690da004a42e20204242a3ad9ad57da7e2712da8487060ac9767f + sha256: "843e317e605e5860e30ae431e9b8724c54f1a8567c9ad495e04595926bf22376" url: "https://pub.dev" source: hosted - version: "7.9.0" + version: "7.13.1" sky_engine: dependency: transitive description: flutter @@ -555,6 +539,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" sqflite: dependency: "direct main" description: @@ -567,18 +559,18 @@ packages: dependency: transitive description: name: sqflite_common - sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a" + sha256: bb4738f15b23352822f4c42a531677e5c6f522e079461fd240ead29d8d8a54a6 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.5.0+2" sqflite_common_ffi: dependency: "direct main" description: name: sqflite_common_ffi - sha256: "0d5cc1be2eb18400ac6701c31211d44164393aa75886093002ecdd947be04f93" + sha256: "35d2fce1e971707c227cc4775cc017d5eafe06c2654c3435ebd5c3ad6c170f5f" url: "https://pub.dev" source: hosted - version: "2.3.0+2" + version: "2.3.0+4" sqlite3: dependency: transitive description: @@ -591,18 +583,18 @@ packages: dependency: "direct main" description: name: sqlite3_flutter_libs - sha256: fb115050b0c2589afe2085a62d77f5deda4db65db20a5c65a6e0c92fda89b45e + sha256: "3e3583b77cf888a68eae2e49ee4f025f66b86623ef0d83c297c8d903daa14871" url: "https://pub.dev" source: hosted - version: "0.5.16" + version: "0.5.18" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" store_checker: dependency: "direct main" description: @@ -615,10 +607,10 @@ packages: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -647,10 +639,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" typed_data: dependency: transitive description: @@ -671,74 +663,98 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27" + sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba url: "https://pub.dev" source: hosted - version: "6.1.14" + version: "6.2.1" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: b04af59516ab45762b2ca6da40fa830d72d0f6045cd97744450b73493fa76330 + sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.2.0" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "7c65021d5dee51813d652357bc65b8dd4a6177082a9966bc8ba6ee477baa795f" + sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "6.2.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: b651aad005e0cb06a01dbd84b428a301916dc75f0e7ea6165f80057fee2d8e8e + sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.1.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: b55486791f666e62e0e8ff825e58a023fd6b1f71c49926483f1128d3bbd8fe88 + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "3.1.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "95465b39f83bfe95fcb9d174829d6476216f2d548b79c38ab2506e0458787618" + sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "2942294a500b4fa0b918685aff406773ba0a4cd34b7f42198742a94083020ce5" + sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" url: "https://pub.dev" source: hosted - version: "2.0.20" + version: "2.2.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "95fef3129dc7cfaba2bc3d5ba2e16063bb561fc6d78e63eee16162bc70029069" + sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.1.0" uuid: dependency: "direct main" description: name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + sha256: df5a4d8f22ee4ccd77f8839ac7cb274ebc11ef9adcce8b92be14b797fe889921 url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "4.2.1" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + url: "https://pub.dev" + source: hosted + version: "1.1.9+1" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + url: "https://pub.dev" + source: hosted + version: "1.1.9+1" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + url: "https://pub.dev" + source: hosted + version: "1.1.9+1" vector_math: dependency: transitive description: @@ -751,26 +767,26 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" win32: dependency: transitive description: name: win32 - sha256: "9e82a402b7f3d518fb9c02d0e9ae45952df31b9bf34d77baf19da2de03fc2aaa" + sha256: "7c99c0e1e2fa190b48d25c81ca5e42036d5cac81430ef249027d97b0935c553f" url: "https://pub.dev" source: hosted - version: "5.0.7" + version: "5.1.0" win32_registry: dependency: transitive description: name: win32_registry - sha256: e4506d60b7244251bc59df15656a3093501c37fb5af02105a944d73eb95be4c9 + sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" window_size: dependency: "direct main" description: @@ -784,10 +800,10 @@ packages: dependency: transitive description: name: xml - sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.4.2" yaml: dependency: transitive description: @@ -797,5 +813,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.3 <4.0.0" + dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index 7fcccf0..482ca92 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,26 +39,26 @@ dependencies: flutter_localizations: sdk: flutter intl: any - provider: ^6.0.3 + provider: ^6.1.1 sqflite: ^2.3.0 - package_info_plus: ^4.0.1 + package_info_plus: ^4.2.0 flutter_displaymode: ^0.6.0 - dynamic_color: ^1.6.5 - animations: ^2.0.5 - device_info_plus: ^9.0.1 - uuid: ^3.0.6 + dynamic_color: ^1.6.8 + animations: ^2.0.8 + device_info_plus: ^9.1.0 + uuid: ^4.2.1 expandable: ^5.0.1 - fl_chart: ^0.62.0 + fl_chart: ^0.64.0 flutter_web_browser: ^0.17.1 - flutter_svg: ^1.1.5 - percent_indicator: ^4.2.2 - store_checker: ^1.2.0 - flutter_markdown: ^0.6.14 - markdown: ^7.0.2 - html: ^0.15.2 - flutter_html: ^3.0.0-alpha.6 - sqlite3_flutter_libs: ^0.5.16 - sqflite_common_ffi: ^2.3.0+2 + flutter_svg: ^2.0.9 + percent_indicator: ^4.2.3 + store_checker: ^1.4.0 + flutter_markdown: ^0.6.18+2 + markdown: ^7.1.1 + html: ^0.15.4 + flutter_html: ^3.0.0-beta.2 + sqlite3_flutter_libs: ^0.5.18 + sqflite_common_ffi: ^2.3.0+4 window_size: git: url: https://github.com/google/flutter-desktop-embedding @@ -69,12 +69,11 @@ dependencies: ref: master-alt url_launcher: ^6.1.11 contextmenu: ^3.0.0 - async: ^2.10.0 - sentry_flutter: ^7.9.0 - flutter_dotenv: ^5.0.2 + async: ^2.11.0 + sentry_flutter: ^7.13.1 + flutter_dotenv: ^5.1.0 flutter_reorderable_list: ^1.3.1 - pie_chart: ^5.3.2 - flutter_hooks: ^0.20.3 + pie_chart: ^5.4.0 segmented_button_slide: ^1.0.4 dev_dependencies: @@ -86,7 +85,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^2.0.0 + flutter_lints: ^3.0.1 flutter_launcher_icons: ^0.13.1 flutter_native_splash: ^2.2.10+1 From ea8b35a0a2e585e6d7b3fdba12244b4e8e56c9d2 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 19 Nov 2023 04:22:59 +0100 Subject: [PATCH 076/177] Icon changes and other improvements --- lib/screens/filters/filters_tabs_view.dart | 2 +- lib/screens/filters/list_options_menu.dart | 4 +- .../enable_disable_selection_modal.dart | 2 +- .../selection/selection_result_modal.dart | 6 +- .../filters/selection/selection_screen.dart | 72 ++++++++----------- lib/widgets/options_modal.dart | 30 ++++---- 6 files changed, 54 insertions(+), 62 deletions(-) diff --git a/lib/screens/filters/filters_tabs_view.dart b/lib/screens/filters/filters_tabs_view.dart index 63e6a4e..f6016e5 100644 --- a/lib/screens/filters/filters_tabs_view.dart +++ b/lib/screens/filters/filters_tabs_view.dart @@ -77,7 +77,7 @@ class _FiltersTabsViewState extends State with TickerProviderSt children: [ const Icon(Icons.verified_user_rounded), const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.whitelists,) + Text(AppLocalizations.of(context)!.whitelists) ], ), ), diff --git a/lib/screens/filters/list_options_menu.dart b/lib/screens/filters/list_options_menu.dart index e2521a3..853eb7c 100644 --- a/lib/screens/filters/list_options_menu.dart +++ b/lib/screens/filters/list_options_menu.dart @@ -26,11 +26,11 @@ 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) { diff --git a/lib/screens/filters/selection/enable_disable_selection_modal.dart b/lib/screens/filters/selection/enable_disable_selection_modal.dart index 08a9b3d..fdb809c 100644 --- a/lib/screens/filters/selection/enable_disable_selection_modal.dart +++ b/lib/screens/filters/selection/enable_disable_selection_modal.dart @@ -29,7 +29,7 @@ class _EnableDisableSelectionModalState extends State createState() => _SelectionScreenState(); @@ -168,13 +168,17 @@ class _SelectionScreenState extends State with TickerProviderSt Row( children: [ IconButton( - onPressed: enableDisableSelected, - icon: const Icon(Icons.shield_rounded), + onPressed: somethingSelected == true + ? () => enableDisableSelected() + : null, + icon: const Icon(Icons.remove_moderator_rounded), tooltip: AppLocalizations.of(context)!.enableDisableSelected, ), const SizedBox(width: 8), IconButton( - onPressed: deleteSelected, + onPressed: somethingSelected == true + ? () => deleteSelected() + : null, icon: const Icon(Icons.delete_rounded), tooltip: AppLocalizations.of(context)!.deleteSelectedLists, ), @@ -270,7 +274,24 @@ class _SelectionScreenState extends State with TickerProviderSt 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), + ], ), ) ]; @@ -296,41 +317,7 @@ class _SelectionScreenState extends State with TickerProviderSt ) ) ), - AnimatedPositioned( - duration: const Duration(milliseconds: 200), - curve: Curves.ease, - right: 16, - bottom: _isScrolled ? -150 : 16, - child: Column( - children: [ - FloatingActionButton.small( - onPressed: somethingSelected == true - ? () => enableDisableSelected() - : null, - tooltip: AppLocalizations.of(context)!.enableDisableSelected, - child: Icon( - Icons.shield_rounded, - color: somethingSelected == true - ? Theme.of(context).colorScheme.onPrimaryContainer - : Theme.of(context).colorScheme.onPrimaryContainer.withOpacity(0.5), - ), - ), - const SizedBox(height: 16), - FloatingActionButton.small( - onPressed: somethingSelected == true - ? () => deleteSelected() - : null, - tooltip: AppLocalizations.of(context)!.deleteSelected, - child: Icon( - Icons.delete_rounded, - color: somethingSelected == true - ? Theme.of(context).colorScheme.onPrimaryContainer - : Theme.of(context).colorScheme.onPrimaryContainer.withOpacity(0.5), - ), - ), - ], - ), - ) + ], ), ); @@ -344,11 +331,10 @@ class _Tab extends StatelessWidget { final int quantity; const _Tab({ - Key? key, required this.icon, required this.text, required this.quantity - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/widgets/options_modal.dart b/lib/widgets/options_modal.dart index 4128fb2..75a55a5 100644 --- a/lib/widgets/options_modal.dart +++ b/lib/widgets/options_modal.dart @@ -8,9 +8,9 @@ class OptionsModal extends StatelessWidget { final List options; const OptionsModal({ - Key? key, + super.key, required this.options, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -32,16 +32,22 @@ class OptionsModal extends StatelessWidget { ) ], ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: options.map((opt) => CustomListTileDialog( - title: opt.title, - icon: opt.icon, - onTap: () { - Navigator.pop(context); - opt.action(); - }, - )).toList() + content: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400 + ), + child: SingleChildScrollView( + child: Wrap( + children: options.map((opt) => CustomListTileDialog( + title: opt.title, + icon: opt.icon, + onTap: () { + Navigator.pop(context); + opt.action(); + }, + )).toList() + ), + ), ), actions: [ Row( From 96d84e1565bf9f5879b4c1acc0c20b08ad394c5b Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 19 Nov 2023 04:24:28 +0100 Subject: [PATCH 077/177] Removed scroll controller --- lib/screens/filters/selection/selection_screen.dart | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/screens/filters/selection/selection_screen.dart b/lib/screens/filters/selection/selection_screen.dart index ec3436f..793a077 100644 --- a/lib/screens/filters/selection/selection_screen.dart +++ b/lib/screens/filters/selection/selection_screen.dart @@ -27,9 +27,6 @@ class SelectionScreen extends StatefulWidget { class _SelectionScreenState extends State with TickerProviderStateMixin { late TabController _tabController; - late ScrollController _scrollController; - - bool _isScrolled = false; List _selectedWhitelists = []; List _selectedBlacklists = []; @@ -42,9 +39,6 @@ class _SelectionScreenState extends State with TickerProviderSt length: 2, vsync: this, ); - _scrollController = ScrollController()..addListener(() { - setState(() => _isScrolled = _scrollController.offset > 20); - }); } void handleSelect(Filter list, ListType type) { @@ -245,7 +239,6 @@ class _SelectionScreenState extends State with TickerProviderSt DefaultTabController( length: 2, child: NestedScrollView( - controller: _scrollController, headerSliverBuilder: ((context, innerBoxIsScrolled) { return [ SliverOverlapAbsorber( From f14828ae198245966aefce673afd31fc8cabe3fd Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 19 Nov 2023 04:56:51 +0100 Subject: [PATCH 078/177] Fixed tabbar position --- lib/providers/clients_provider.dart | 18 +- lib/screens/filters/filters_tabs_view.dart | 5 +- .../filters/selection/selection_lists.dart | 3 +- .../filters/selection/selection_screen.dart | 1 - .../access_settings/access_settings.dart | 188 ++++++----- .../access_settings/add_client_modal.dart | 316 +++++++++--------- .../access_settings/clients_list.dart | 28 +- 7 files changed, 306 insertions(+), 253 deletions(-) diff --git a/lib/providers/clients_provider.dart b/lib/providers/clients_provider.dart index 8cf79a5..a06d27c 100644 --- a/lib/providers/clients_provider.dart +++ b/lib/providers/clients_provider.dart @@ -8,6 +8,8 @@ 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; @@ -195,20 +197,20 @@ class ClientsProvider with ChangeNotifier { } } - Future> addClientList(String item, String type) async { + Future> addClientList(String item, AccessSettingsList type) async { Map> 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']!.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); } @@ -239,20 +241,20 @@ class ClientsProvider with ChangeNotifier { } } - Future> removeClientList(String client, String type) async { + Future> removeClientList(String client, AccessSettingsList type) async { Map> 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(); } diff --git a/lib/screens/filters/filters_tabs_view.dart b/lib/screens/filters/filters_tabs_view.dart index f6016e5..bc96bc8 100644 --- a/lib/screens/filters/filters_tabs_view.dart +++ b/lib/screens/filters/filters_tabs_view.dart @@ -18,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 createState() => _FiltersTabsViewState(); @@ -70,6 +70,7 @@ class _FiltersTabsViewState extends State with TickerProviderSt controller: tabController, isScrollable: true, unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, + tabAlignment: TabAlignment.start, tabs: [ Tab( child: Row( diff --git a/lib/screens/filters/selection/selection_lists.dart b/lib/screens/filters/selection/selection_lists.dart index 1525e24..9562e74 100644 --- a/lib/screens/filters/selection/selection_lists.dart +++ b/lib/screens/filters/selection/selection_lists.dart @@ -152,11 +152,10 @@ class _Tile extends StatelessWidget { final bool isSelected; const _Tile({ - Key? key, required this.list, required this.onSelect, required this.isSelected, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/screens/filters/selection/selection_screen.dart b/lib/screens/filters/selection/selection_screen.dart index 793a077..108544e 100644 --- a/lib/screens/filters/selection/selection_screen.dart +++ b/lib/screens/filters/selection/selection_screen.dart @@ -310,7 +310,6 @@ class _SelectionScreenState extends State with TickerProviderSt ) ) ), - ], ), ); diff --git a/lib/screens/settings/access_settings/access_settings.dart b/lib/screens/settings/access_settings/access_settings.dart index ca9cf12..d5673f7 100644 --- a/lib/screens/settings/access_settings/access_settings.dart +++ b/lib/screens/settings/access_settings/access_settings.dart @@ -11,105 +11,38 @@ import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; class AccessSettings extends StatefulWidget { - const AccessSettings({Key? key}) : super(key: key); + const AccessSettings({super.key}); @override State createState() => _AccessSettingsState(); } class _AccessSettingsState extends State with TickerProviderStateMixin { - final ScrollController scrollController = ScrollController(); - late TabController tabController; + late ScrollController _scrollController; + late TabController _tabController; @override void initState() { Provider.of(context, listen: false).fetchClients(updateLoading: true); super.initState(); - tabController = TabController( + _tabController = TabController( initialIndex: 0, length: 3, vsync: this, ); + _scrollController = ScrollController(); } @override Widget build(BuildContext context) { - final clientsProvider = Provider.of(context); - final width = MediaQuery.of(context).size.width; - Widget body() { - return TabBarView( - controller: tabController, - children: [ - ClientsList( - type: 'allowed', - scrollController: scrollController, - loadStatus: clientsProvider.loadStatus, - data: clientsProvider.loadStatus == LoadStatus.loaded - ? clientsProvider.clients!.clientsAllowedBlocked!.allowedClients : [], - ), - ClientsList( - type: 'disallowed', - scrollController: scrollController, - loadStatus: clientsProvider.loadStatus, - data: clientsProvider.loadStatus == LoadStatus.loaded - ? clientsProvider.clients!.clientsAllowedBlocked!.disallowedClients : [], - ), - ClientsList( - type: 'domains', - scrollController: scrollController, - loadStatus: clientsProvider.loadStatus, - data: clientsProvider.loadStatus == LoadStatus.loaded - ? clientsProvider.clients!.clientsAllowedBlocked!.blockedHosts : [], - ), - ] - ); - } - - PreferredSizeWidget tabBar() { - return TabBar( - controller: tabController, - isScrollable: true, - unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, - tabs: [ - Tab( - child: Row( - children: [ - const Icon(Icons.check), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.allowedClients) - ], - ), - ), - Tab( - child: Row( - children: [ - const Icon(Icons.block), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.disallowedClients) - ], - ), - ), - Tab( - child: Row( - children: [ - const Icon(Icons.link_rounded), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.disallowedDomains) - ], - ), - ), - ] - ); - } - if (Platform.isAndroid || Platform.isIOS) { return Scaffold( body: DefaultTabController( length: 3, child: NestedScrollView( - controller: scrollController, + controller: _scrollController, headerSliverBuilder: ((context, innerBoxIsScrolled) { return [ SliverOverlapAbsorber( @@ -123,13 +56,19 @@ class _AccessSettingsState extends State with TickerProviderStat centerTitle: false, forceElevated: innerBoxIsScrolled, surfaceTintColor: isDesktop(width) ? Colors.transparent : null, - bottom: tabBar() + bottom: PreferredSize( + preferredSize: const Size(double.maxFinite, 50), + child: _Tabs(tabController: _tabController) + ) ), ), ) ]; }), - body: body() + body: _TabsView( + tabController: _tabController, + scrollController: _scrollController + ) ) ), ); @@ -139,10 +78,105 @@ class _AccessSettingsState extends State with TickerProviderStat appBar: AppBar( title: Text(AppLocalizations.of(context)!.accessSettings), centerTitle: false, - bottom: tabBar() + bottom: PreferredSize( + preferredSize: const Size(double.maxFinite, 50), + child: _Tabs(tabController: _tabController) + ) ), - body: body(), + body: _TabsView( + tabController: _tabController, + scrollController: _scrollController + ) ); } } +} + +class _Tabs extends StatelessWidget { + final TabController tabController; + + const _Tabs({ + required this.tabController, + }); + + @override + Widget build(BuildContext context) { + return TabBar( + controller: tabController, + isScrollable: true, + unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, + tabAlignment: TabAlignment.start, + tabs: [ + Tab( + child: Row( + children: [ + const Icon(Icons.check), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.allowedClients) + ], + ), + ), + Tab( + child: Row( + children: [ + const Icon(Icons.block), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.disallowedClients) + ], + ), + ), + Tab( + child: Row( + children: [ + const Icon(Icons.link_rounded), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.disallowedDomains) + ], + ), + ), + ] + ); + } +} + +class _TabsView extends StatelessWidget { + final TabController tabController; + final ScrollController scrollController; + + const _TabsView({ + required this.tabController, + required this.scrollController, + }); + + @override + Widget build(BuildContext context) { + final clientsProvider = Provider.of(context); + + return TabBarView( + controller: tabController, + children: [ + ClientsList( + type: AccessSettingsList.allowed, + scrollController: scrollController, + loadStatus: clientsProvider.loadStatus, + data: clientsProvider.loadStatus == LoadStatus.loaded + ? clientsProvider.clients!.clientsAllowedBlocked!.allowedClients : [], + ), + ClientsList( + type: AccessSettingsList.disallowed, + scrollController: scrollController, + loadStatus: clientsProvider.loadStatus, + data: clientsProvider.loadStatus == LoadStatus.loaded + ? clientsProvider.clients!.clientsAllowedBlocked!.disallowedClients : [], + ), + ClientsList( + type: AccessSettingsList.domains, + scrollController: scrollController, + loadStatus: clientsProvider.loadStatus, + data: clientsProvider.loadStatus == LoadStatus.loaded + ? clientsProvider.clients!.clientsAllowedBlocked!.blockedHosts : [], + ), + ] + ); + } } \ No newline at end of file diff --git a/lib/screens/settings/access_settings/add_client_modal.dart b/lib/screens/settings/access_settings/add_client_modal.dart index 14ba98a..014babd 100644 --- a/lib/screens/settings/access_settings/add_client_modal.dart +++ b/lib/screens/settings/access_settings/add_client_modal.dart @@ -1,162 +1,23 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -class AddClientModal extends StatefulWidget { - final String type; - final void Function(String, String) onConfirm; +import 'package:adguard_home_manager/providers/clients_provider.dart'; + +class AddClientModal extends StatelessWidget { + final AccessSettingsList type; + final void Function(String, AccessSettingsList) onConfirm; final bool dialog; const AddClientModal({ - Key? key, + super.key, required this.type, required this.onConfirm, required this.dialog, - }) : super(key: key); + }); - @override - State createState() => _AddClientModalState(); -} - -class _AddClientModalState extends State { - TextEditingController fieldController = TextEditingController(); - - bool validData = false; - - void checkValidValues() { - if (fieldController.text != '') { - setState(() => validData = true); - } - else { - setState(() => validData = false); - } - } - @override Widget build(BuildContext context) { - IconData icon() { - switch (widget.type) { - case 'allowed': - return Icons.check; - - case 'disallowed': - return Icons.block; - - case 'domains': - return Icons.link_rounded; - - default: - return Icons.check; - } - } - - String title() { - switch (widget.type) { - case 'allowed': - return AppLocalizations.of(context)!.allowClient; - - case 'disallowed': - return AppLocalizations.of(context)!.disallowClient; - - case 'domains': - return AppLocalizations.of(context)!.disallowedDomains; - - default: - return ""; - } - } - - Widget content() { - return Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: SingleChildScrollView( - child: Wrap( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - icon(), - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - ], - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - title(), - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ), - ), - TextFormField( - controller: fieldController, - onChanged: (_) => checkValidValues(), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - helperText: widget.type == 'allowed' || widget.type == 'disallowed' - ? AppLocalizations.of(context)!.addClientFieldDescription : null, - labelText: widget.type == 'allowed' || widget.type == 'disallowed' - ? AppLocalizations.of(context)!.clientIdentifier - : AppLocalizations.of(context)!.domain, - ), - ), - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.only(top: 24), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) - ), - const SizedBox(width: 16), - TextButton( - onPressed: validData == true - ? () { - Navigator.pop(context); - widget.onConfirm(fieldController.text, widget.type); - } - : null, - child: Text( - AppLocalizations.of(context)!.confirm, - style: TextStyle( - color: validData == true - ? Theme.of(context).colorScheme.primary - : Colors.grey - ), - ) - ), - ], - ), - ), - ], - ), - ); - } - - if (widget.dialog == true) { + if (dialog == true) { return Padding( padding: MediaQuery.of(context).viewInsets, child: Dialog( @@ -164,7 +25,10 @@ class _AddClientModalState extends State { constraints: const BoxConstraints( maxWidth: 400 ), - child: content() + child: _Content( + type: type, + onConfirm: onConfirm, + ) ), ), ); @@ -180,9 +44,163 @@ class _AddClientModalState extends State { topRight: Radius.circular(28) ) ), - child: content() + child: _Content( + type: type, + onConfirm: onConfirm, + ) ), ); } } +} + +class _Content extends StatefulWidget { + final AccessSettingsList type; + final void Function(String, AccessSettingsList) onConfirm; + + const _Content({ + required this.type, + required this.onConfirm, + }); + + @override + State<_Content> createState() => _ContentState(); +} + +class _ContentState extends State<_Content> { + TextEditingController fieldController = TextEditingController(); + bool validData = false; + + void checkValidValues() { + if (fieldController.text != '') { + setState(() => validData = true); + } + else { + setState(() => validData = false); + } + } + + @override + Widget build(BuildContext context) { + IconData icon() { + switch (widget.type) { + case AccessSettingsList.allowed: + return Icons.check; + + case AccessSettingsList.disallowed: + return Icons.block; + + case AccessSettingsList.domains: + return Icons.link_rounded; + + default: + return Icons.check; + } + } + + String title() { + switch (widget.type) { + case AccessSettingsList.allowed: + return AppLocalizations.of(context)!.allowClient; + + case AccessSettingsList.disallowed: + return AppLocalizations.of(context)!.disallowClient; + + case AccessSettingsList.domains: + return AppLocalizations.of(context)!.disallowedDomains; + + default: + return ""; + } + } + + return Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SingleChildScrollView( + child: Wrap( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + icon(), + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + title(), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ), + TextFormField( + controller: fieldController, + onChanged: (_) => checkValidValues(), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.link_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + helperText: widget.type == AccessSettingsList.allowed || widget.type == AccessSettingsList.disallowed + ? AppLocalizations.of(context)!.addClientFieldDescription : null, + labelText: widget.type == AccessSettingsList.allowed || widget.type == AccessSettingsList.disallowed + ? AppLocalizations.of(context)!.clientIdentifier + : AppLocalizations.of(context)!.domain, + ), + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ), + const SizedBox(width: 16), + TextButton( + onPressed: validData == true + ? () { + Navigator.pop(context); + widget.onConfirm(fieldController.text, widget.type); + } + : null, + child: Text( + AppLocalizations.of(context)!.confirm, + style: TextStyle( + color: validData == true + ? Theme.of(context).colorScheme.primary + : Colors.grey + ), + ) + ), + ], + ), + ), + ], + ), + ); + } } \ No newline at end of file diff --git a/lib/screens/settings/access_settings/clients_list.dart b/lib/screens/settings/access_settings/clients_list.dart index fcffbef..a9b1662 100644 --- a/lib/screens/settings/access_settings/clients_list.dart +++ b/lib/screens/settings/access_settings/clients_list.dart @@ -18,18 +18,18 @@ import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; class ClientsList extends StatefulWidget { - final String type; + final AccessSettingsList type; final ScrollController scrollController; final LoadStatus loadStatus; final List data; const ClientsList({ - Key? key, + super.key, required this.type, required this.scrollController, required this.loadStatus, required this.data, - }) : super(key: key); + }); @override State createState() => _ClientsListState(); @@ -79,20 +79,20 @@ class _ClientsListState extends State { } } - void confirmRemoveItem(String client, String type) async { + void confirmRemoveItem(String client, AccessSettingsList type) async { Map> body = { "allowed_clients": clientsProvider.clients!.clientsAllowedBlocked?.allowedClients ?? [], "disallowed_clients": clientsProvider.clients!.clientsAllowedBlocked?.disallowedClients ?? [], "blocked_hosts": clientsProvider.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(); } @@ -128,7 +128,7 @@ class _ClientsListState extends State { } } - void confirmAddItem(String item, String type) async { + void confirmAddItem(String item, AccessSettingsList type) async { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.removingClient); @@ -163,13 +163,13 @@ class _ClientsListState extends State { String description() { switch (widget.type) { - case 'allowed': + case AccessSettingsList.allowed: return AppLocalizations.of(context)!.allowedClientsDescription; - case 'disallowed': + case AccessSettingsList.disallowed: return AppLocalizations.of(context)!.blockedClientsDescription; - case 'domains': + case AccessSettingsList.domains: return AppLocalizations.of(context)!.disallowedDomainsDescription; default: @@ -179,13 +179,13 @@ class _ClientsListState extends State { String noItems() { switch (widget.type) { - case 'allowed': + case AccessSettingsList.allowed: return AppLocalizations.of(context)!.noAllowedClients; - case 'disallowed': + case AccessSettingsList.disallowed: return AppLocalizations.of(context)!.noBlockedClients; - case 'domains': + case AccessSettingsList.domains: return AppLocalizations.of(context)!.noDisallowedDomains; default: From e161cfb59446c22b6283456ae8c1c6ab0f0e93f8 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 19 Nov 2023 22:52:40 +0100 Subject: [PATCH 079/177] Changed api requests --- lib/classes/http_client.dart | 200 +++++ lib/functions/check_app_updates.dart | 12 +- lib/functions/clear_dns_cache.dart | 15 +- lib/providers/clients_provider.dart | 63 +- lib/providers/dhcp_provider.dart | 36 +- lib/providers/dns_provider.dart | 106 +-- lib/providers/filtering_provider.dart | 100 +-- lib/providers/rewrite_rules_provider.dart | 18 +- lib/providers/servers_provider.dart | 40 +- lib/providers/status_provider.dart | 96 +-- .../filters/details/check_host_modal.dart | 6 +- lib/screens/filters/filters.dart | 4 +- .../management_modal/management_modal.dart | 5 +- .../logs/configuration/logs_config_modal.dart | 94 +-- lib/screens/logs/logs_list_appbar.dart | 24 +- .../access_settings/clients_list.dart | 12 +- lib/screens/settings/dhcp/dhcp.dart | 17 +- lib/screens/settings/dhcp/dhcp_leases.dart | 6 +- lib/screens/settings/dns/bootstrap_dns.dart | 6 +- lib/screens/settings/dns/cache_config.dart | 8 +- lib/screens/settings/dns/dns.dart | 2 +- .../settings/dns/dns_server_settings.dart | 6 +- .../settings/dns/private_reverse_servers.dart | 4 +- lib/screens/settings/dns/upstream_dns.dart | 6 +- .../settings/encryption/encryption.dart | 136 ++-- .../settings/update_server/update.dart | 4 +- lib/services/api_client.dart | 764 ++++++++++++++++++ lib/services/external_requests.dart | 80 ++ lib/services/http_requests.dart | 215 ----- lib/widgets/add_server/add_server_modal.dart | 6 +- .../servers_list/servers_list_item.dart | 14 +- .../servers_list/servers_tile_item.dart | 11 +- 32 files changed, 1376 insertions(+), 740 deletions(-) create mode 100644 lib/classes/http_client.dart create mode 100644 lib/services/api_client.dart create mode 100644 lib/services/external_requests.dart diff --git a/lib/classes/http_client.dart b/lib/classes/http_client.dart new file mode 100644 index 0000000..f14b839 --- /dev/null +++ b/lib/classes/http_client.dart @@ -0,0 +1,200 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:sentry_flutter/sentry_flutter.dart'; + +import 'package:adguard_home_manager/models/server.dart'; + +enum ExceptionType { socket, timeout, handshake, 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 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 + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const HttpResponse( + successful: false, + body: null, + statusCode: null, + exception: ExceptionType.unknown + ); + } + } + + static Future 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 HandshakeException { + return const HttpResponse( + successful: false, + body: null, + statusCode: null, + exception: ExceptionType.handshake + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const HttpResponse( + successful: false, + body: null, + statusCode: null, + exception: ExceptionType.unknown + ); + } + } + + static Future 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 HandshakeException { + return const HttpResponse( + successful: false, + body: null, + statusCode: null, + exception: ExceptionType.handshake + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const HttpResponse( + successful: false, + body: null, + statusCode: null, + exception: ExceptionType.unknown + ); + } + } +} \ No newline at end of file diff --git a/lib/functions/check_app_updates.dart b/lib/functions/check_app_updates.dart index 306b2ea..7ab8352 100644 --- a/lib/functions/check_app_updates.dart +++ b/lib/functions/check_app_updates.dart @@ -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 checkAppUpdates({ required String currentBuildNumber, @@ -13,16 +13,16 @@ Future checkAppUpdates({ required bool isBeta }) async { var result = isBeta - ? await getReleasesGitHub() - : await getLatestReleaseGitHub(); + ? await ExternalRequests.getReleasesGitHub() + : await ExternalRequests.getLatestReleaseGitHub(); - if (result['result'] == 'success') { + if (result.successful == true) { late GitHubRelease gitHubRelease; if (isBeta) { - gitHubRelease = (result['body'] as List).firstWhere((r) => r.prerelease == true); + gitHubRelease = (result.content as List).firstWhere((r) => r.prerelease == true); } else { - gitHubRelease = result['body'] as GitHubRelease; + gitHubRelease = result.content as GitHubRelease; } final update = gitHubUpdateExists( diff --git a/lib/functions/clear_dns_cache.dart b/lib/functions/clear_dns_cache.dart index 0ff1606..d0b44cd 100644 --- a/lib/functions/clear_dns_cache.dart +++ b/lib/functions/clear_dns_cache.dart @@ -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 clearDnsCache(BuildContext context, Server server) async { +Future clearDnsCache(BuildContext context, Server server) async { final serversProvider = Provider.of(context, listen: false); final ProcessModal processModal = ProcessModal(context: context); 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(context, listen: false); - appConfigProvider.addLog(result['log']); - return false; - } + return result; } \ No newline at end of file diff --git a/lib/providers/clients_provider.dart b/lib/providers/clients_provider.dart index a06d27c..ae9cb28 100644 --- a/lib/providers/clients_provider.dart +++ b/lib/providers/clients_provider.dart @@ -1,5 +1,6 @@ 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'; @@ -105,9 +106,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; @@ -122,9 +123,9 @@ class ClientsProvider with ChangeNotifier { } Future 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); @@ -138,7 +139,7 @@ class ClientsProvider with ChangeNotifier { } Future editClient(Client client) async { - final result = await _serversProvider!.apiClient!.postUpdateClient( + final result = await _serversProvider!.apiClient2!.postUpdateClient( data: { 'name': client.name, 'data': serverVersionIsAhead( @@ -151,7 +152,7 @@ class ClientsProvider with ChangeNotifier { } ); - if (result['result'] == 'success') { + if (result.successful == true) { Clients clientsData = clients!; clientsData.clients = clientsData.clients.map((e) { if (e.name == client.name) { @@ -173,7 +174,7 @@ class ClientsProvider with ChangeNotifier { } Future addClient(Client client) async { - final result = await _serversProvider!.apiClient!.postAddClient( + final result = await _serversProvider!.apiClient2!.postAddClient( data: serverVersionIsAhead( currentVersion: _statusProvider!.serverStatus!.serverVersion, referenceVersion: 'v0.107.28', @@ -183,7 +184,7 @@ class ClientsProvider with ChangeNotifier { : removePropFromMap(client.toJson(), 'safe_search') ); - if (result['result'] == 'success') { + if (result.successful == true) { Clients clientsData = clients!; clientsData.clients.add(client); setClientsData(clientsData, false); @@ -197,7 +198,7 @@ class ClientsProvider with ChangeNotifier { } } - Future> addClientList(String item, AccessSettingsList type) async { + Future addClientList(String item, AccessSettingsList type) async { Map> body = { "allowed_clients": clients!.clientsAllowedBlocked?.allowedClients ?? [], "disallowed_clients": clients!.clientsAllowedBlocked?.disallowedClients ?? [], @@ -214,34 +215,30 @@ class ClientsProvider with ChangeNotifier { 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> removeClientList(String client, AccessSettingsList type) async { + Future removeClientList(String client, AccessSettingsList type) async { Map> body = { "allowed_clients": clients!.clientsAllowedBlocked?.allowedClients ?? [], "disallowed_clients": clients!.clientsAllowedBlocked?.disallowedClients ?? [], @@ -258,30 +255,26 @@ class ClientsProvider with ChangeNotifier { 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; } } } \ No newline at end of file diff --git a/lib/providers/dhcp_provider.dart b/lib/providers/dhcp_provider.dart index 2c2e7d9..637f870 100644 --- a/lib/providers/dhcp_provider.dart +++ b/lib/providers/dhcp_provider.dart @@ -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 deleteLease(Lease lease) async { - final result = await _serversProvider!.apiClient!.deleteStaticLease( + final result = await _serversProvider!.apiClient2!.deleteStaticLease( data: { "mac": lease.mac, "ip": lease.ip, @@ -66,7 +67,7 @@ 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(); setDhcpData(data); @@ -78,8 +79,8 @@ class DhcpProvider with ChangeNotifier { } } - Future> createLease(Lease lease) async { - final result = await _serversProvider!.apiClient!.createStaticLease( + Future 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); 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; } } } \ No newline at end of file diff --git a/lib/providers/dns_provider.dart b/lib/providers/dns_provider.dart index 0965725..db1767f 100644 --- a/lib/providers/dns_provider.dart +++ b/lib/providers/dns_provider.dart @@ -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'; @@ -58,12 +59,12 @@ class DnsProvider with ChangeNotifier { } } - Future> savePrivateReverseServersConfig(Map value) async { - final result = await _serversProvider!.apiClient!.setDnsConfig( + Future savePrivateReverseServersConfig(Map 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,71 @@ 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> saveUpstreamDnsConfig(Map value) async { - final result = await _serversProvider!.apiClient!.setDnsConfig( + Future saveUpstreamDnsConfig(Map value) async { + final result = await _serversProvider!.apiClient2!.setDnsConfig( data: value ); - if (result['result'] == 'success') { + if (result.successful == true) { DnsInfo data = dnsInfo!; data.upstreamDns = List.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> saveBootstrapDnsConfig(Map value) async { - final result = await _serversProvider!.apiClient!.setDnsConfig( + Future saveBootstrapDnsConfig(Map value) async { + final result = await _serversProvider!.apiClient2!.setDnsConfig( data: value ); - if (result['result'] == 'success') { + if (result.successful == true) { DnsInfo data = dnsInfo!; data.bootstrapDns = List.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> saveCacheCacheConfig(Map value) async { - final result = await _serversProvider!.apiClient!.setDnsConfig( + Future saveCacheCacheConfig(Map value) async { + final result = await _serversProvider!.apiClient2!.setDnsConfig( data: value ); - if (result['result'] == 'success') { + 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> saveDnsServerConfig(Map value) async { - final result = await _serversProvider!.apiClient!.setDnsConfig( + Future saveDnsServerConfig(Map 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']; @@ -181,19 +146,10 @@ class DnsProvider with ChangeNotifier { data.blockingIpv4 = value['blocking_ipv4']; data.blockingIpv6 = value['blocking_ipv6']; 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; + } } } \ No newline at end of file diff --git a/lib/providers/filtering_provider.dart b/lib/providers/filtering_provider.dart index 75cfbed..a264429 100644 --- a/lib/providers/filtering_provider.dart +++ b/lib/providers/filtering_provider.dart @@ -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); 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> 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 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 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 removeCustomRule(String rule) async { final List 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 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> 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("data is HTML, not plain text")) { + 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('url already exists')) { + 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); _blockedServicesLoadStatus = LoadStatus.loaded; notifyListeners(); @@ -371,11 +373,11 @@ class FilteringProvider with ChangeNotifier { } Future updateBlockedServices(List 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; } @@ -393,13 +395,13 @@ class FilteringProvider with ChangeNotifier { required Filter list, required bool isWhitelist, }) async { - final result = await _serversProvider!.apiClient!.deleteFilterList( + final result = await _serversProvider!.apiClient2!.deleteFilterList( data: { "url": list.url, "whitelist": isWhitelist } ); - if (result['result'] == 'success') { + if (result.successful == true) { return ProcessedList(list: list, successful: true); } else { @@ -426,7 +428,7 @@ class FilteringProvider with ChangeNotifier { required Filter list, required bool isWhitelist, }) async { - final result = await _serversProvider!.apiClient!.updateFilterList( + final result = await _serversProvider!.apiClient2!.updateFilterList( data: { "data": { "enabled": !list.enabled, @@ -437,7 +439,7 @@ class FilteringProvider with ChangeNotifier { "whitelist": isWhitelist } ); - if (result['result'] == 'success') { + if (result.successful == true) { return ProcessedList(list: list, successful: true); } else { diff --git a/lib/providers/rewrite_rules_provider.dart b/lib/providers/rewrite_rules_provider.dart index 1e3bda3..ec8ea1d 100644 --- a/lib/providers/rewrite_rules_provider.dart +++ b/lib/providers/rewrite_rules_provider.dart @@ -35,14 +35,14 @@ class RewriteRulesProvider with ChangeNotifier { } Future 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 data = rewriteRules!; data.add(rule); setRewriteRulesData(data); @@ -55,7 +55,7 @@ class RewriteRulesProvider with ChangeNotifier { } Future 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 data = rewriteRules!; final index = data.indexOf(oldRule); data[index] = newRule; @@ -82,14 +82,14 @@ class RewriteRulesProvider with ChangeNotifier { } Future 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 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; _loadStatus = LoadStatus.loaded; notifyListeners(); return true; diff --git a/lib/providers/servers_provider.dart b/lib/providers/servers_provider.dart index 3a0e1ad..600c46e 100644 --- a/lib/providers/servers_provider.dart +++ b/lib/providers/servers_provider.dart @@ -3,6 +3,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:sqflite/sqflite.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'; @@ -16,6 +18,7 @@ class ServersProvider with ChangeNotifier { List _serversList = []; Server? _selectedServer; ApiClient? _apiClient; + ApiClientV2? _apiClient2; bool _updatingServer = false; @@ -28,6 +31,10 @@ class ServersProvider with ChangeNotifier { return _apiClient; } + ApiClientV2? get apiClient2 { + return _apiClient2; + } + List get serversList { return _serversList; } @@ -75,6 +82,11 @@ class ServersProvider with ChangeNotifier { notifyListeners(); } + void setApiClient2(ApiClientV2 client) { + _apiClient2 = client; + notifyListeners(); + } + void setUpdatingServer(bool status) { _updatingServer = status; notifyListeners(); @@ -176,9 +188,9 @@ class ServersProvider with ChangeNotifier { final result = await client!.checkServerUpdates(); if (result['result'] == 'success') { UpdateAvailableData data = UpdateAvailableData.fromJson(result['data']); - final gitHubResult = await client.getUpdateChangelog(releaseTag: data.newVersion ?? data.currentVersion); - if (gitHubResult['result'] == 'success') { - data.changelog = gitHubResult['body']; + final gitHubResult = await ExternalRequests.getUpdateChangelog(releaseTag: data.newVersion ?? data.currentVersion); + if (gitHubResult.successful == true) { + data.changelog = gitHubResult.content; } setUpdateAvailableData(data); setUpdateAvailableLoadStatus(LoadStatus.loaded, true); @@ -188,9 +200,9 @@ class ServersProvider with ChangeNotifier { } } - Future initializateServer(Server server, ApiClient apiClient) 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: apiClient @@ -226,8 +238,10 @@ class ServersProvider with ChangeNotifier { if (defaultServer != null) { _selectedServer = defaultServer; final client = ApiClient(server: defaultServer); + final client2 = ApiClientV2(server: defaultServer); _apiClient = client; - initializateServer(defaultServer, client); + _apiClient2 = client2; + initializateServer(defaultServer, client, client2); } } else { @@ -244,13 +258,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.getUpdateChangelog(releaseTag: data.newVersion ?? data.currentVersion); + if (gitHubResult.successful == true) { + data.changelog = gitHubResult.content; } setUpdateAvailableData(data); timer.cancel(); diff --git a/lib/providers/status_provider.dart b/lib/providers/status_provider.dart index 5a4ef73..38193b8 100644 --- a/lib/providers/status_provider.dart +++ b/lib/providers/status_provider.dart @@ -6,7 +6,6 @@ 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 +103,7 @@ class StatusProvider with ChangeNotifier { } } - Future updateBlocking({ + Future updateBlocking({ required String block, required bool newStatus, int? time @@ -114,14 +113,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,32 +134,12 @@ 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(); @@ -174,72 +153,61 @@ class StatusProvider with ChangeNotifier { if (result['result'] == 'success') { _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 +220,9 @@ class StatusProvider with ChangeNotifier { } Future 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; } @@ -270,10 +238,10 @@ class StatusProvider with ChangeNotifier { _loadStatus = LoadStatus.loading; } - final result = await _serversProvider!.apiClient!.getServerStatus(); - if (result['result'] == 'success') { + final result = await _serversProvider!.apiClient2!.getServerStatus(); + if (result.successful == true) { setServerStatusData( - data: result['data'] + data: result.content as ServerStatus ); _loadStatus = LoadStatus.loaded; notifyListeners(); @@ -292,12 +260,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 newRules = rules['data'].userRules.where((d) => !d.contains(domain)).toList(); + List newRules = (rules.content as FilteringStatus).userRules.where((d) => !d.contains(domain)).toList(); if (newStatus == 'block') { newRules.add("||$domain^"); } @@ -308,9 +276,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 +292,11 @@ class StatusProvider with ChangeNotifier { } Future updateSafeSearchConfig(Map 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; diff --git a/lib/screens/filters/details/check_host_modal.dart b/lib/screens/filters/details/check_host_modal.dart index 90e93ab..5325ada 100644 --- a/lib/screens/filters/details/check_host_modal.dart +++ b/lib/screens/filters/details/check_host_modal.dart @@ -58,11 +58,11 @@ class _CheckHostModalState extends State { void checkHost() async { setState(() => resultWidget = checking()); - final result = await serversProvider.apiClient!.checkHostFiltered(host: domainController.text); + final result = await serversProvider.apiClient2!.checkHostFiltered(host: domainController.text); if (mounted) { - if (result['result'] == 'success') { - final status = getFilteredStatus(context, appConfigProvider, result['data']['reason'], true); + if (result.successful == true) { + final status = getFilteredStatus(context, appConfigProvider, result.content['reason'], true); if (mounted) { setState(() => resultWidget = Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/screens/filters/filters.dart b/lib/screens/filters/filters.dart index 6d811a2..7953b81 100644 --- a/lib/screens/filters/filters.dart +++ b/lib/screens/filters/filters.dart @@ -53,11 +53,9 @@ class _FiltersState extends State { void updateLists() async { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.updatingLists); - final result = await filteringProvider.updateLists(); - + if (!mounted) return; processModal.close(); - if (result['success'] == true) { showSnacbkar( appConfigProvider: appConfigProvider, diff --git a/lib/screens/home/management_modal/management_modal.dart b/lib/screens/home/management_modal/management_modal.dart index 8fa968c..5096a9d 100644 --- a/lib/screens/home/management_modal/management_modal.dart +++ b/lib/screens/home/management_modal/management_modal.dart @@ -81,10 +81,7 @@ class _ManagementModalState extends State with SingleTickerProv newStatus: value, time: time ); - if (mounted && result != null) { - if (result != false) { - appConfigProvider.addLog(result); - } + if (mounted && result == false) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.invalidUsernamePassword, diff --git a/lib/screens/logs/configuration/logs_config_modal.dart b/lib/screens/logs/configuration/logs_config_modal.dart index 8db61ba..fb260b9 100644 --- a/lib/screens/logs/configuration/logs_config_modal.dart +++ b/lib/screens/logs/configuration/logs_config_modal.dart @@ -7,8 +7,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/logs/configuration/config_widgets.dart'; import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/functions/compare_versions.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; class RetentionItem { @@ -21,62 +19,27 @@ class RetentionItem { }); } -class LogsConfigModal extends StatelessWidget { - final void Function(Map) onConfirm; - final void Function() onClear; - final bool dialog; - final String serverVersion; - - const LogsConfigModal({ - Key? key, - required this.onConfirm, - required this.onClear, - required this.dialog, - required this.serverVersion - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final serversProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - - return LogsConfigModalWidget( - serversProvider: serversProvider, - appConfigProvider: appConfigProvider, - context: context, - onConfirm: onConfirm, - onClear: onClear, - dialog: dialog, - serverVersion: serverVersion, - ); - } -} - -class LogsConfigModalWidget extends StatefulWidget { - final ServersProvider serversProvider; - final AppConfigProvider appConfigProvider; +class LogsConfigModal extends StatefulWidget { final BuildContext context; final void Function(Map) onConfirm; final void Function() onClear; final bool dialog; final String serverVersion; - const LogsConfigModalWidget({ - Key? key, - required this.serversProvider, - required this.appConfigProvider, + const LogsConfigModal({ + super.key, required this.context, required this.onConfirm, required this.onClear, required this.dialog, required this.serverVersion - }) : super(key: key); + }); @override - State createState() => _LogsConfigModalWidgetState(); + State createState() => _LogsConfigModalState(); } -class _LogsConfigModalWidgetState extends State { +class _LogsConfigModalState extends State { bool generalSwitch = false; bool anonymizeClientIp = false; double? retentionTime; @@ -88,21 +51,15 @@ class _LogsConfigModalWidgetState extends State { void loadData() async { final serversProvider = Provider.of(context, listen: false); - final result = serverVersionIsAhead( - currentVersion: widget.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == true - ? await serversProvider.apiClient!.getQueryLogInfo() - : await serversProvider.apiClient!.getQueryLogInfoLegacy(); + final result = await serversProvider.apiClient2!.getQueryLogInfo(); if (mounted) { - if (result['result'] == 'success') { + if (result.successful == true) { setState(() { - generalSwitch = result['data']['enabled']; - anonymizeClientIp = result['data']['anonymize_client_ip']; - retentionTime = result['data']['interval'] != null - ? double.parse(result['data']['interval'].toString()) + generalSwitch = result.content['enabled']; + anonymizeClientIp = result.content['anonymize_client_ip']; + retentionTime = result.content['interval'] != null + ? double.parse(result.content['interval'].toString()) : null; loadStatus = LoadStatus.loaded; }); @@ -115,11 +72,7 @@ class _LogsConfigModalWidgetState extends State { @override void initState() { - retentionItems = serverVersionIsAhead( - currentVersion: widget.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == true ? [ + retentionItems = [ RetentionItem( label: AppLocalizations.of(widget.context)!.hours6, value: 21600000 @@ -140,27 +93,6 @@ class _LogsConfigModalWidgetState extends State { label: AppLocalizations.of(widget.context)!.days90, value: 7776000000 ), - ] : [ - RetentionItem( - label: AppLocalizations.of(widget.context)!.hours6, - value: 0.25 - ), - RetentionItem( - label: AppLocalizations.of(widget.context)!.hours24, - value: 1 - ), - RetentionItem( - label: AppLocalizations.of(widget.context)!.days7, - value: 7 - ), - RetentionItem( - label: AppLocalizations.of(widget.context)!.days30, - value: 30 - ), - RetentionItem( - label: AppLocalizations.of(widget.context)!.days90, - value: 90 - ), ]; loadData(); diff --git a/lib/screens/logs/logs_list_appbar.dart b/lib/screens/logs/logs_list_appbar.dart index 8e02aed..7b99435 100644 --- a/lib/screens/logs/logs_list_appbar.dart +++ b/lib/screens/logs/logs_list_appbar.dart @@ -25,10 +25,10 @@ class LogsListAppBar extends StatelessWidget { final bool showDivider; const LogsListAppBar({ - Key? key, + super.key, required this.innerBoxIsScrolled, required this.showDivider, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -43,17 +43,11 @@ class LogsListAppBar extends StatelessWidget { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.updatingSettings); - final result = serverVersionIsAhead( - currentVersion: statusProvider.serverStatus!.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == true - ? await serversProvider.apiClient!.updateQueryLogParameters(data: data) - : await serversProvider.apiClient!.updateQueryLogParametersLegacy(data: data); - + final result = await serversProvider.apiClient2!.updateQueryLogParameters(data: data); + processModal.close(); - if (result['result'] == 'success') { + if (result.successful == true) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.logsConfigUpdated, @@ -61,8 +55,6 @@ class LogsListAppBar extends StatelessWidget { ); } else { - appConfigProvider.addLog(result['log']); - showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.logsConfigNotUpdated, @@ -75,11 +67,11 @@ class LogsListAppBar extends StatelessWidget { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.updatingSettings); - final result = await serversProvider.apiClient!.clearLogs(); + final result = await serversProvider.apiClient2!.clearLogs(); processModal.close(); - if (result['result'] == 'success') { + if (result.successful == true) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.logsCleared, @@ -159,6 +151,7 @@ class LogsListAppBar extends StatelessWidget { showDialog( context: context, builder: (context) => LogsConfigModal( + context: context, onConfirm: updateConfig, onClear: clearQueries, dialog: true, @@ -172,6 +165,7 @@ class LogsListAppBar extends StatelessWidget { context: context, useRootNavigator: true, builder: (context) => LogsConfigModal( + context: context, onConfirm: updateConfig, onClear: clearQueries, dialog: false, diff --git a/lib/screens/settings/access_settings/clients_list.dart b/lib/screens/settings/access_settings/clients_list.dart index a9b1662..4dedc24 100644 --- a/lib/screens/settings/access_settings/clients_list.dart +++ b/lib/screens/settings/access_settings/clients_list.dart @@ -103,14 +103,14 @@ class _ClientsListState extends State { processModal.close(); - if (result['success'] == true) { + if (result.successful == true) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientRemovedSuccessfully, color: Colors.green ); } - else if (result['success'] == false && result['error'] == 'client_another_list') { + else if (result.successful == false && result.content == 'client_another_list') { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientAnotherList, @@ -120,7 +120,7 @@ class _ClientsListState extends State { else { showSnacbkar( appConfigProvider: appConfigProvider, - label: type == 'allowed' || type == 'blocked' + label: type == AccessSettingsList.allowed || type == AccessSettingsList.disallowed ? AppLocalizations.of(context)!.clientNotRemoved : AppLocalizations.of(context)!.domainNotAdded, color: Colors.red @@ -136,14 +136,14 @@ class _ClientsListState extends State { processModal.close(); - if (result['success'] == true) { + if (result.successful == true) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientAddedSuccessfully, color: Colors.green ); } - else if (result['success'] == false && result['error'] == 'client_another_list') { + else if (result.successful == false && result.content == 'client_another_list') { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.clientAnotherList, @@ -153,7 +153,7 @@ class _ClientsListState extends State { else { showSnacbkar( appConfigProvider: appConfigProvider, - label: type == 'allowed' || type == 'blocked' + label: type == AccessSettingsList.allowed || type == AccessSettingsList.disallowed ? AppLocalizations.of(context)!.clientNotRemoved : AppLocalizations.of(context)!.domainNotAdded, color: Colors.red diff --git a/lib/screens/settings/dhcp/dhcp.dart b/lib/screens/settings/dhcp/dhcp.dart index c003ea2..6440e3b 100644 --- a/lib/screens/settings/dhcp/dhcp.dart +++ b/lib/screens/settings/dhcp/dhcp.dart @@ -194,8 +194,7 @@ class _DhcpScreenState extends State { void saveSettings() async { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.savingSettings); - - final result = await serversProvider.apiClient!.saveDhcpConfig( + final result = await serversProvider.apiClient2!.saveDhcpConfig( data: { "enabled": enabled, "interface_name": selectedInterface!.name, @@ -213,10 +212,9 @@ class _DhcpScreenState extends State { } } ); - + if (!mounted) return; processModal.close(); - - if (result['result'] == 'success') { + if (result.successful == true) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.settingsSaved, @@ -236,14 +234,11 @@ class _DhcpScreenState extends State { Future.delayed(const Duration(seconds: 0), () async { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.restoringConfig); - - final result = await serversProvider.apiClient!.resetDhcpConfig(); - + final result = await serversProvider.apiClient2!.resetDhcpConfig(); + if (!mounted) return; processModal.close(); - - if (result['result'] == 'success') { + if (result.successful == true) { clearAll(); - showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.configRestored, diff --git a/lib/screens/settings/dhcp/dhcp_leases.dart b/lib/screens/settings/dhcp/dhcp_leases.dart index 60a5644..d6f5051 100644 --- a/lib/screens/settings/dhcp/dhcp_leases.dart +++ b/lib/screens/settings/dhcp/dhcp_leases.dart @@ -66,21 +66,21 @@ class DhcpLeases extends StatelessWidget { processModal.close(); - if (result['success'] == true) { + if (result.successful == true) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.staticLeaseCreated, color: Colors.green ); } - else if (result['success'] == false && result['error'] == 'already_exists' ) { + else if (result.successful == false && result.content == "already_exists") { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.staticLeaseExists, color: Colors.red ); } - else if (result['success'] == false && result['error'] == 'server_not_configured' ) { + else if (result.successful == false && result.content == "server_not_configured") { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.serverNotConfigured, diff --git a/lib/screens/settings/dns/bootstrap_dns.dart b/lib/screens/settings/dns/bootstrap_dns.dart index 291601a..a439367 100644 --- a/lib/screens/settings/dns/bootstrap_dns.dart +++ b/lib/screens/settings/dns/bootstrap_dns.dart @@ -11,7 +11,7 @@ import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class BootstrapDnsScreen extends StatefulWidget { - const BootstrapDnsScreen({Key? key}) : super(key: key); + const BootstrapDnsScreen({super.key}); @override State createState() => _BootstrapDnsScreenState(); @@ -79,14 +79,14 @@ class _BootstrapDnsScreenState extends State { processModal.close(); - if (result['success'] == true) { + if (result.successful == true) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsConfigSaved, color: Colors.green ); } - else if (result['success'] == false && result['error'] == 400) { + else if (result.successful == false && result.statusCode == 400) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.someValueNotValid, diff --git a/lib/screens/settings/dns/cache_config.dart b/lib/screens/settings/dns/cache_config.dart index b092a8b..13d1836 100644 --- a/lib/screens/settings/dns/cache_config.dart +++ b/lib/screens/settings/dns/cache_config.dart @@ -16,7 +16,7 @@ import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class CacheConfigDnsScreen extends StatefulWidget { - const CacheConfigDnsScreen({Key? key}) : super(key: key); + const CacheConfigDnsScreen({super.key}); @override State createState() => _CacheConfigDnsScreenState(); @@ -85,14 +85,14 @@ class _CacheConfigDnsScreenState extends State { processModal.close(); - if (result['success'] == true) { + if (result.successful == true) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsConfigSaved, color: Colors.green ); } - else if (result['success'] == false && result['error'] == 400) { + else if (result.successful== false && result.statusCode == 400) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.someValueNotValid, @@ -138,7 +138,7 @@ class _CacheConfigDnsScreenState extends State { void clearCache() async { final result = await clearDnsCache(context, serversProvider.selectedServer!); - if (result == true) { + if (result.successful == true) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsCacheCleared, diff --git a/lib/screens/settings/dns/dns.dart b/lib/screens/settings/dns/dns.dart index 1f74669..02ec5d6 100644 --- a/lib/screens/settings/dns/dns.dart +++ b/lib/screens/settings/dns/dns.dart @@ -63,7 +63,7 @@ class _DnsSettingsState extends State { void clearCache() async { final result = await clearDnsCache(context, serversProvider.selectedServer!); - if (result == true) { + if (result.successful == true) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsCacheCleared, diff --git a/lib/screens/settings/dns/dns_server_settings.dart b/lib/screens/settings/dns/dns_server_settings.dart index b8481a0..6602af0 100644 --- a/lib/screens/settings/dns/dns_server_settings.dart +++ b/lib/screens/settings/dns/dns_server_settings.dart @@ -15,7 +15,7 @@ import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class DnsServerSettingsScreen extends StatefulWidget { - const DnsServerSettingsScreen({Key? key}) : super(key: key); + const DnsServerSettingsScreen({super.key}); @override State createState() => _DnsServerSettingsScreenState(); @@ -118,14 +118,14 @@ class _DnsServerSettingsScreenState extends State { processModal.close(); - if (result['success'] == true) { + if (result.successful == true) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsConfigSaved, color: Colors.green ); } - else if (result['success'] == false && result['error'] == 400) { + else if (result.successful == false && result.statusCode == 400) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.someValueNotValid, diff --git a/lib/screens/settings/dns/private_reverse_servers.dart b/lib/screens/settings/dns/private_reverse_servers.dart index 2373d44..98b1cb9 100644 --- a/lib/screens/settings/dns/private_reverse_servers.dart +++ b/lib/screens/settings/dns/private_reverse_servers.dart @@ -111,14 +111,14 @@ class _PrivateReverseDnsServersScreenState extends State createState() => _UpstreamDnsScreenState(); @@ -151,14 +151,14 @@ class _UpstreamDnsScreenState extends State { processModal.close(); - if (result['success'] == true) { + if (result.successful == true) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.dnsConfigSaved, color: Colors.green ); } - else if (result['success'] == false && result['error'] == 400) { + else if (result.successful == false && result.statusCode == 400) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.someValueNotValid, diff --git a/lib/screens/settings/encryption/encryption.dart b/lib/screens/settings/encryption/encryption.dart index 3c4744c..fc38216 100644 --- a/lib/screens/settings/encryption/encryption.dart +++ b/lib/screens/settings/encryption/encryption.dart @@ -13,6 +13,8 @@ import 'package:adguard_home_manager/screens/settings/encryption/master_switch.d import 'package:adguard_home_manager/screens/settings/encryption/encryption_functions.dart'; import 'package:adguard_home_manager/screens/settings/encryption/error_message.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/models/encryption.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/base64.dart'; @@ -20,37 +22,16 @@ import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; -class EncryptionSettings extends StatelessWidget { - const EncryptionSettings({Key? key}) : super(key: key); +class EncryptionSettings extends StatefulWidget { + const EncryptionSettings({super.key}); + @override - Widget build(BuildContext context) { - final serversProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - - return EncryptionSettingsWidget( - serversProvider: serversProvider, - appConfigProvider: appConfigProvider, - ); - } + State createState() => _EncryptionSettingsState(); } -class EncryptionSettingsWidget extends StatefulWidget { - final ServersProvider serversProvider; - final AppConfigProvider appConfigProvider; - - const EncryptionSettingsWidget({ - Key? key, - required this.serversProvider, - required this.appConfigProvider, - }) : super(key: key); - - @override - State createState() => _EncryptionSettingsWidgetState(); -} - -class _EncryptionSettingsWidgetState extends State { - int loadStatus = 0; +class _EncryptionSettingsState extends State { + LoadStatus loadStatus = LoadStatus.loading; bool enabled = false; @@ -96,54 +77,52 @@ class _EncryptionSettingsWidgetState extends State { bool formEdited = false; void fetchData({bool? showRefreshIndicator}) async { - setState(() => loadStatus = 0); + setState(() => loadStatus = LoadStatus.loading); - final result = await Provider.of(context, listen: false).apiClient!.getEncryptionSettings(); + final result = await Provider.of(context, listen: false).apiClient2!.getEncryptionSettings(); + if (!mounted) return; - if (mounted) { - if (result['result'] == 'success') { - await checkValidDataApi(data: result['data'].toJson()); + final data = result.content as EncryptionData; + + if (result.successful == true) { + await checkValidDataApi(data: data.toJson()); + if (!mounted) return; - if (mounted) { - setState(() { - enabled = result['data'].enabled; - domainNameController.text = result['data'].serverName ?? ''; - redirectHttps = result['data'].forceHttps; - httpsPortController.text = result['data'].portHttps != null ? result['data'].portHttps.toString() : ''; - tlsPortController.text = result['data'].portDnsOverTls != null ? result['data'].portDnsOverTls.toString() : ''; - dnsOverQuicPortController.text = result['data'].portDnsOverQuic != null ? result['data'].portDnsOverQuic.toString() : ''; - if (result['data'].certificateChain != '') { - certificateOption = 1; - certificateContentController.text = decodeBase64(result['data'].certificateChain); - } - else { - certificateOption = 0; - certificatePathController.text = result['data'].certificatePath; - } - if (result['data'].privateKey != '' || result['data'].privateKeySaved == true) { - privateKeyOption = 1; - } - else { - privateKeyOption = 0; - privateKeyPathController.text = result['data'].privateKeyPath; - } - usePreviouslySavedKey = result['data'].privateKeySaved; - - loadStatus = 1; - }); + setState(() { + enabled = data.enabled; + domainNameController.text = data.serverName ?? ''; + redirectHttps = data.forceHttps ?? false; + httpsPortController.text = data.portHttps != null ? data.portHttps.toString() : ''; + tlsPortController.text = data.portDnsOverTls != null ? data.portDnsOverTls.toString() : ''; + dnsOverQuicPortController.text = data.portDnsOverQuic != null ? data.portDnsOverQuic.toString() : ''; + if (data.certificateChain != '') { + certificateOption = 1; + certificateContentController.text = decodeBase64(data.certificateChain); } - } - else { - widget.appConfigProvider.addLog(result['log']); - setState(() => loadStatus = 2); - } + else { + certificateOption = 0; + certificatePathController.text = data.certificatePath; + } + if (data.privateKey != '' || data.privateKeySaved == true) { + privateKeyOption = 1; + } + else { + privateKeyOption = 0; + privateKeyPathController.text = data.privateKeyPath; + } + usePreviouslySavedKey = data.privateKeySaved; + loadStatus = LoadStatus.loaded; + }); + } + else { + setState(() => loadStatus = LoadStatus.error); } } Future checkValidDataApi({Map? data}) async { setState(() => certKeyValidApi = 0); - final result = await Provider.of(context, listen: false).apiClient!.checkEncryptionSettings( + final result = await Provider.of(context, listen: false).apiClient2!.checkEncryptionSettings( data: data ?? { "enabled": enabled, "server_name": domainNameController.text, @@ -160,23 +139,20 @@ class _EncryptionSettingsWidgetState extends State { ); if (mounted) { - if (result['result'] == 'success') { + if (result.successful == true) { setState(() { - if (result['data']['warning_validation'] != null && result['data']['warning_validation'] != '') { + if (result.content['warning_validation'] != null && result.content['warning_validation'] != '') { certKeyValidApi = 2; - validDataError = result['data']['warning_validation']; + validDataError = result.content['warning_validation']; } else { certKeyValidApi = 1; validDataError = null; } - certKeyValid = result['data']; + certKeyValid = result.content; }); } else { - if (result['log'].resBody != null) { - setState(() => validDataError = result['log'].resBody); - } setState(() => certKeyValidApi = 2); } } @@ -228,7 +204,7 @@ class _EncryptionSettingsWidgetState extends State { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.savingConfig); - final result = await serversProvider.apiClient!.saveEncryptionSettings( + final result = await serversProvider.apiClient2!.saveEncryptionSettings( data: { "enabled": enabled, "server_name": domainNameController.text, @@ -246,7 +222,7 @@ class _EncryptionSettingsWidgetState extends State { processModal.close(); - if (result['result'] == 'success') { + if (result.successful == true) { showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.encryptionConfigSaved, @@ -254,19 +230,17 @@ class _EncryptionSettingsWidgetState extends State { ); } else { - appConfigProvider.addLog(result['log']); - showSnacbkar( appConfigProvider: appConfigProvider, label: AppLocalizations.of(context)!.encryptionConfigNotSaved, color: Colors.red ); - if (result['log'].resBody != null) { + if (result.content != null) { showDialog( context: context, builder: (context) => ErrorMessageEncryption( - errorMessage: result['log'].resBody + errorMessage: result.content ) ); } @@ -303,7 +277,7 @@ class _EncryptionSettingsWidgetState extends State { body: Builder( builder: (context) { switch (loadStatus) { - case 0: + case LoadStatus.loading: return SizedBox( width: double.maxFinite, child: Column( @@ -324,7 +298,7 @@ class _EncryptionSettingsWidgetState extends State { ) ); - case 1: + case LoadStatus.loaded: return ListView( children: [ EncryptionMasterSwitch( @@ -641,7 +615,7 @@ class _EncryptionSettingsWidgetState extends State { ], ); - case 2: + case LoadStatus.error: return SizedBox( width: double.maxFinite, child: Column( diff --git a/lib/screens/settings/update_server/update.dart b/lib/screens/settings/update_server/update.dart index 3467994..f1c63a8 100644 --- a/lib/screens/settings/update_server/update.dart +++ b/lib/screens/settings/update_server/update.dart @@ -35,11 +35,11 @@ class UpdateScreen extends StatelessWidget { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.requestingUpdate); - final result = await serversProvider.apiClient!.requestUpdateServer(); + final result = await serversProvider.apiClient2!.requestUpdateServer(); processModal.close(); - if (result['result'] == 'success') { + if (result.successful == true) { serversProvider.recheckPeriodServerUpdated(); showSnacbkar( appConfigProvider: appConfigProvider, diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart new file mode 100644 index 0000000..692739c --- /dev/null +++ b/lib/services/api_client.dart @@ -0,0 +1,764 @@ +import 'dart:convert'; + +import 'package:sentry_flutter/sentry_flutter.dart'; + +import 'package:adguard_home_manager/models/blocked_services.dart'; +import 'package:adguard_home_manager/models/dns_info.dart'; +import 'package:adguard_home_manager/models/encryption.dart'; +import 'package:adguard_home_manager/models/dhcp.dart'; +import 'package:adguard_home_manager/models/rewrite_rules.dart'; +import 'package:adguard_home_manager/models/filtering.dart'; +import 'package:adguard_home_manager/models/filtering_status.dart'; +import 'package:adguard_home_manager/models/server_info.dart'; +import 'package:adguard_home_manager/models/logs.dart'; +import 'package:adguard_home_manager/models/clients.dart'; +import 'package:adguard_home_manager/models/clients_allowed_blocked.dart'; +import 'package:adguard_home_manager/models/server.dart'; +import 'package:adguard_home_manager/models/server_status.dart'; +import 'package:adguard_home_manager/classes/http_client.dart'; + +class ApiResponse { + final bool successful; + final dynamic content; + final int? statusCode; + + const ApiResponse({ + required this.successful, + this.content, + this.statusCode, + }); +} + +class ApiClientV2 { + final Server server; + + ApiClientV2({ + required this.server + }); + + Future getServerStatus() async { + final results = await Future.wait([ + HttpRequestClient.get(urlPath: "/stats", server: server), + HttpRequestClient.get(urlPath: "/status", server: server), + HttpRequestClient.get(urlPath: "/filtering/status", server: server), + HttpRequestClient.get(urlPath: "/safesearch/status", server: server), + HttpRequestClient.get(urlPath: "/safebrowsing/status", server: server), + HttpRequestClient.get(urlPath: "/parental/status", server: server), + HttpRequestClient.get(urlPath: "/clients", server: server), + ]); + + if ( + results.map((e) => e.successful).every((e) => e == true) && + results.map((e) => e.body).every((e) => e != null) + ) { + final Map mappedData = { + 'stats': jsonDecode(results[0].body!), + 'clients': jsonDecode(results[6].body!)['clients'], + 'status': jsonDecode(results[1].body!), + 'filtering': jsonDecode(results[2].body!), + 'safeSearch': jsonDecode(results[3].body!), + 'safeBrowsingEnabled': jsonDecode(results[4].body!), + 'parentalControlEnabled': jsonDecode(results[5].body!), + }; + return ApiResponse( + successful: true, + content: ServerStatus.fromJson(mappedData) + ); + } + else { + return const ApiResponse(successful: false); + } + } + + Future updateFiltering({ + required bool enable + }) async { + final result = await HttpRequestClient.post( + urlPath: "/filtering/config", + server: server, + body: { + 'enabled': enable + } + ); + return ApiResponse( + successful: result.successful, + ); + } + + Future updateSafeBrowsing({ + required bool enable + }) async { + final result = await HttpRequestClient.post( + urlPath: enable == true + ? "/safebrowsing/enable" + : "/safebrowsing/disable", + server: server, + ); + return ApiResponse( + successful: result.successful, + ); + } + + Future updateParentalControl({ + required bool enable + }) async { + final result = await HttpRequestClient.post( + urlPath: enable == true + ? "/parental/enable" + : "/parental/disable", + server: server, + ); + return ApiResponse( + successful: result.successful, + ); + } + + Future updateGeneralProtection({ + required bool enable, + int? time, + }) async { + final result = await HttpRequestClient.post( + urlPath: "/protection", + server: server, + body: { + 'enabled': enable, + 'duration': time + } + ); + return ApiResponse( + successful: result.successful, + ); + } + + Future getClients() async { + final results = await Future.wait([ + HttpRequestClient.get(urlPath: "/clients", server: server), + HttpRequestClient.get(urlPath: "/access/list", server: server), + ]); + if ( + results.map((e) => e.successful).every((e) => e == true) && + results.map((e) => e.body).every((e) => e != null) + ) { + try { + final clients = Clients.fromJson(jsonDecode(results[0].body!)); + clients.clientsAllowedBlocked = ClientsAllowedBlocked.fromJson(jsonDecode(results[1].body!)); + return ApiResponse( + successful: true, + content: clients + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } + else { + return const ApiResponse(successful: false); + } + } + + Future requestAllowedBlockedClientsHosts({ + required Map?> body + }) async { + final result = await HttpRequestClient.post( + urlPath: "/access/set", + server: server, + body: body + ); + if (result.statusCode == 400) { + return const ApiResponse( + successful: false, + content: "client_another_list" + ); + } + return ApiResponse(successful: result.successful); + } + + Future getLogs({ + required int count, + int? offset, + DateTime? olderThan, + String? responseStatus, + String? search + }) async { + final result = await HttpRequestClient.get( + urlPath: '/querylog?limit=$count${offset != null ? '&offset=$offset' : ''}${olderThan != null ? '&older_than=${olderThan.toIso8601String()}' : ''}${responseStatus != null ? '&response_status=$responseStatus' : ''}${search != null ? '&search=$search' : ''}', + server: server + ); + if (result.successful == true) { + try { + return ApiResponse( + successful: true, + content: LogsData.fromJson(jsonDecode(result.body!)) + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } + else { + return const ApiResponse(successful: false); + } + } + + Future getFilteringRules() async { + final result = await HttpRequestClient.get(urlPath: '/filtering/status', server: server); + if (result.successful == true) { + try { + return ApiResponse( + successful: true, + content: FilteringStatus.fromJson(jsonDecode(result.body!)) + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } + else { + return const ApiResponse(successful: false); + } + } + + Future postFilteringRules({ + required Map> data, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/filtering/set_rules', + server: server, + body: data + ); + return ApiResponse(successful: result.successful); + } + + Future postAddClient({ + required Map data, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/clients/add', + server: server, + body: data + ); + return ApiResponse(successful: result.successful); + } + + Future postUpdateClient({ + required Map data, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/clients/update', + server: server, + body: data + ); + return ApiResponse(successful: result.successful); + } + + Future postDeleteClient({ + required String name, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/clients/delete', + server: server, + body: {'name': name}, + ); + return ApiResponse(successful: result.successful); + } + + Future getFiltering() async { + final results = await Future.wait([ + HttpRequestClient.get(urlPath: '/filtering/status', server: server), + HttpRequestClient.get(urlPath: '/blocked_services/list', server: server), + ]); + if (results[0].successful == true && results[0].body != null) { + try { + return ApiResponse( + successful: true, + content: Filtering.fromJson({ + ...jsonDecode(results[0].body!), + "blocked_services": results[1].body != null + ? jsonDecode(results[1].body!) + : [] + }) + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } + else { + return const ApiResponse(successful: false); + } + } + + Future setCustomRules({ + required List rules, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/filtering/set_rules', + server: server, + body: {'rules': rules}, + ); + return ApiResponse(successful: result.successful); + } + + Future addFilteringList({ + required Map data, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/filtering/add_url', + server: server, + body: data, + ); + return ApiResponse( + successful: result.successful, + content: result.body, + statusCode: result.statusCode + ); + } + + Future updateFilterList({ + required Map data, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/filtering/set_url', + server: server, + body: data, + ); + return ApiResponse(successful: result.successful); + } + + Future deleteFilterList({ + required Map data, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/filtering/remove_url', + server: server, + body: data, + ); + return ApiResponse(successful: result.successful); + } + + Future getServerInfo() async { + final result = await HttpRequestClient.get(urlPath: "/status", server: server); + if (result.successful) { + try { + return ApiResponse( + successful: true, + content: ServerInfoData.fromJson(jsonDecode(result.body!)) + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } + else { + return const ApiResponse(successful: false); + } + } + + Future updateLists() async { + final results = await Future.wait([ + HttpRequestClient.post( + urlPath: '/filtering/refresh', + server: server, + body: {'whitelist': true}, + ), + HttpRequestClient.post( + urlPath: '/filtering/refresh', + server: server, + body: {'whitelist': false}, + ), + ]); + if ( + results.map((e) => e.successful).every((e) => e == true) && + results.map((e) => e.body).every((e) => e != null) + ) { + try { + final clients = Clients.fromJson(jsonDecode(results[0].body!)); + clients.clientsAllowedBlocked = ClientsAllowedBlocked.fromJson(jsonDecode(results[1].body!)); + return ApiResponse( + successful: true, + content: {'updated': jsonDecode(results[0].body!)['updated']+jsonDecode(results[1].body!)['updated']} + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } + else { + return const ApiResponse(successful: false); + } + } + + Future checkHostFiltered({ + required String host + }) async { + final result = await HttpRequestClient.get(urlPath: '/filtering/check_host?name=$host', server: server); + if (result.successful) { + return ApiResponse( + successful: true, + content: jsonDecode(result.body!) + ); + } + else { + return const ApiResponse(successful: false); + } + } + + Future requestChangeUpdateFrequency({ + required Map data, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/filtering/config', + server: server, + body: data, + ); + return ApiResponse(successful: result.successful); + } + + Future setBlockedServices({ + required List data, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/blocked_services/set', + server: server, + body: data, + ); + return ApiResponse(successful: result.successful); + } + + Future getDhcpData() async { + final results = await Future.wait([ + HttpRequestClient.get(urlPath: '/dhcp/interfaces', server: server), + HttpRequestClient.get(urlPath: '/dhcp/status', server: server), + ]); + if ( + results.map((e) => e.successful).every((e) => e == true) && + results.map((e) => e.body).every((e) => e != null) + ) { + try { + List interfaces = List.from(jsonDecode(results[0].body!).entries.map((entry) => NetworkInterface.fromJson(entry.value))); + return ApiResponse( + successful: true, + content: DhcpModel( + networkInterfaces: interfaces, + dhcpStatus: DhcpStatus.fromJson(jsonDecode(results[1].body!)) + ) + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } + else { + return const ApiResponse(successful: false); + } + } + + Future saveDhcpConfig({ + required Map data, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/dhcp/set_config', + server: server, + body: data, + ); + return ApiResponse(successful: result.successful); + } + + Future resetDhcpConfig() async { + final result = await HttpRequestClient.post( + urlPath: '/dhcp/reset', + server: server, + body: {}, + ); + return ApiResponse(successful: result.successful); + } + + Future deleteStaticLease({ + required Map data + }) async { + final result = await HttpRequestClient.post( + urlPath: '/dhcp/remove_static_lease', + server: server, + body: data, + ); + return ApiResponse(successful: result.successful); + } + + Future createStaticLease({ + required Map data + }) async { + final result = await HttpRequestClient.post( + urlPath: '/dhcp/add_static_lease', + server: server, + body: data, + ); + if (result.statusCode == 400 && result.body != null && result.body!.contains('static lease already exists')) { + return const ApiResponse( + successful: false, + content: "already_exists", + statusCode: 400 + ); + } + if (result.statusCode == 400 && result.body != null && result.body!.contains('server is unconfigured')) { + return const ApiResponse( + successful: false, + content: "server_not_configured", + statusCode: 400 + ); + } + return ApiResponse(successful: result.successful); + } + + Future restoreAllLeases() async { + final result = await HttpRequestClient.post( + urlPath: '/dhcp/reset_leases', + server: server, + body: {}, + ); + return ApiResponse(successful: result.successful); + } + + Future getDnsRewriteRules() async { + final result = await HttpRequestClient.get(urlPath: '/rewrite/list', server: server); + if (result.successful) { + try { + final List data = List.from( + jsonDecode(result.body!).map((item) => RewriteRules.fromJson(item)) + ); + return ApiResponse( + successful: true, + content: data + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } + else { + return const ApiResponse(successful: false); + } + } + + Future deleteDnsRewriteRule({ + required Map data, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/rewrite/delete', + server: server, + body: data, + ); + return ApiResponse(successful: result.successful); + } + + Future addDnsRewriteRule({ + required Map data, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/rewrite/add', + server: server, + body: data, + ); + return ApiResponse(successful: result.successful); + } + + Future getQueryLogInfo() async { + final result = await HttpRequestClient.get(urlPath: '/querylog/config', server: server); + if (result.successful) { + return ApiResponse( + successful: true, + content: jsonDecode(result.body!) + ); + } + else { + return const ApiResponse(successful: false); + } + } + + Future updateQueryLogParameters({ + required Map data, + }) async { + final result = await HttpRequestClient.put( + urlPath: '/querylog/config/update', + server: server, + body: data, + ); + return ApiResponse(successful: result.successful); + } + + Future clearLogs() async { + final result = await HttpRequestClient.put( + urlPath: '/querylog_clear', + server: server, + body: {}, + ); + return ApiResponse(successful: result.successful); + } + + Future getDnsInfo() async { + final result = await HttpRequestClient.get(urlPath: '/dns_info', server: server); + if (result.successful) { + try { + return ApiResponse( + successful: true, + content: DnsInfo.fromJson(jsonDecode(result.body!)) + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } + else { + return const ApiResponse(successful: false); + } + } + + Future setDnsConfig({ + required Map data, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/dns_config', + server: server, + body: data, + ); + if (result.statusCode == 400) { + return ApiResponse( + successful: result.successful, + content: "data_not_valid", + statusCode: result.statusCode + ); + } + return ApiResponse(successful: result.successful); + } + + Future getEncryptionSettings() async { + final result = await HttpRequestClient.get(urlPath: '/tls/status', server: server); + if (result.successful) { + try { + return ApiResponse( + successful: true, + content: EncryptionData.fromJson(jsonDecode(result.body!)) + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } + else { + return const ApiResponse(successful: false); + } + } + + Future getBlockedServices() async { + final result = await HttpRequestClient.get(urlPath: '/blocked_services/all', server: server); + if (result.successful) { + try { + return ApiResponse( + successful: true, + content: List.from( + BlockedServicesFromApi.fromJson(jsonDecode(result.body!)).blockedServices + ) + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } + else { + return const ApiResponse(successful: false); + } + } + + Future checkEncryptionSettings({ + required Map data, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/tls/validate', + server: server, + body: data, + ); + return ApiResponse( + successful: result.successful, + content: result.body != null ? jsonDecode(result.body!) : null + ); + } + + Future saveEncryptionSettings({ + required Map data, + }) async { + final result = await HttpRequestClient.post( + urlPath: '/tls/configure', + server: server, + body: data, + ); + return ApiResponse( + successful: result.successful, + content: result.body + ); + } + + Future resetDnsCache() async { + final result = await HttpRequestClient.post( + urlPath: '/cache_clear', + server: server, + ); + return ApiResponse(successful: result.successful); + } + + Future checkServerUpdates() async { + final results = await Future.wait([ + HttpRequestClient.post(urlPath: '/version.json', server: server, body: { "recheck_now": true }), + HttpRequestClient.get(urlPath: '/status', server: server), + ]); + if ( + results.map((e) => e.successful).every((e) => e == true) && + results.map((e) => e.body).every((e) => e != null) + ) { + try { + final Map obj = { + ...jsonDecode(results[0].body!), + 'current_version': ServerInfoData.fromJson(jsonDecode(results[1].body!)).version + }; + return ApiResponse( + successful: true, + content: obj + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } + else { + return const ApiResponse(successful: false); + } + } + + Future requestUpdateServer() async { + final result = await HttpRequestClient.post( + urlPath: '/update', + server: server, + ); + return ApiResponse(successful: result.successful); + } + + Future updateSafeSearchSettings({ + required Map body + }) async { + final result = await HttpRequestClient.put( + urlPath: '/safesearch/settings', + server: server, + body: body + ); + return ApiResponse(successful: result.successful); + } + + Future updateRewriteRule({ + required Map body + }) async { + final result = await HttpRequestClient.put( + urlPath: '/rewrite/update', + server: server, + body: body + ); + return ApiResponse(successful: result.successful); + } +} \ No newline at end of file diff --git a/lib/services/external_requests.dart b/lib/services/external_requests.dart new file mode 100644 index 0000000..b63b6fb --- /dev/null +++ b/lib/services/external_requests.dart @@ -0,0 +1,80 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:sentry_flutter/sentry_flutter.dart'; + +import 'package:adguard_home_manager/models/github_release.dart'; +import 'package:adguard_home_manager/constants/urls.dart'; +import 'package:adguard_home_manager/services/api_client.dart'; + +class ExternalRequests { + static Future getUpdateChangelog({ + required String releaseTag + }) async { + try { + HttpClient httpClient = HttpClient(); + HttpClientRequest request = await httpClient.getUrl(Uri.parse("${Urls.adGuardHomeReleasesTags}/$releaseTag")); + HttpClientResponse response = await request.close(); + String reply = await response.transform(utf8.decoder).join(); + httpClient.close(); + if (response.statusCode == 200) { + return ApiResponse( + successful: true, + content: jsonDecode(reply)['body'], + statusCode: response.statusCode + ); + } + else { + return const ApiResponse(successful: false); + } + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } + + static Future getReleasesGitHub() async { + try { + HttpClient httpClient = HttpClient(); + HttpClientRequest request = await httpClient.getUrl(Uri.parse(Urls.getReleasesGitHub)); + HttpClientResponse response = await request.close(); + String reply = await response.transform(utf8.decoder).join(); + httpClient.close(); + if (response.statusCode == 200) { + return ApiResponse( + successful: true, + content: List.from(jsonDecode(reply).map((entry) => GitHubRelease.fromJson(entry))) + ); + } + else { + return const ApiResponse(successful: false); + } + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } + + static Future getLatestReleaseGitHub() async { + try { + HttpClient httpClient = HttpClient(); + HttpClientRequest request = await httpClient.getUrl(Uri.parse(Urls.getLatestReleaseGitHub)); + HttpClientResponse response = await request.close(); + String reply = await response.transform(utf8.decoder).join(); + httpClient.close(); + if (response.statusCode == 200) { + return ApiResponse( + successful: true, + content: GitHubRelease.fromJson(jsonDecode(reply)), + statusCode: response.statusCode + ); + } + else { + return const ApiResponse(successful: false); + } + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } +} \ No newline at end of file diff --git a/lib/services/http_requests.dart b/lib/services/http_requests.dart index a2eb5ec..8063b4f 100644 --- a/lib/services/http_requests.dart +++ b/lib/services/http_requests.dart @@ -2165,79 +2165,6 @@ class ApiClient { } } - Future getUpdateChangelog({ - required String releaseTag - }) async { - try { - HttpClient httpClient = HttpClient(); - HttpClientRequest request = await httpClient.getUrl(Uri.parse("${Urls.adGuardHomeReleasesTags}/$releaseTag")); - HttpClientResponse response = await request.close(); - String reply = await response.transform(utf8.decoder).join(); - httpClient.close(); - if (response.statusCode == 200) { - return { - 'result': 'success', - 'hasResponse': true, - 'error': false, - 'statusCode': response.statusCode, - 'body': jsonDecode(reply)['body'] - }; - } - else { - return { - 'result': 'error', - 'log': AppLog( - type: 'update_encryption_settings', - dateTime: DateTime.now(), - message: 'error_code_not_expected', - statusCode: response.statusCode.toString(), - resBody: reply, - ) - }; - } - } on SocketException { - return { - 'result': 'no_connection', - 'message': 'SocketException', - 'log': AppLog( - type: 'check_latest_release_github', - dateTime: DateTime.now(), - message: 'SocketException' - ) - }; - } on TimeoutException { - return { - 'result': 'no_connection', - 'message': 'TimeoutException', - 'log': AppLog( - type: 'check_latest_release_github', - dateTime: DateTime.now(), - message: 'TimeoutException' - ) - }; - } on HandshakeException { - return { - 'result': 'ssl_error', - 'message': 'HandshakeException', - 'log': AppLog( - type: 'check_latest_release_github', - dateTime: DateTime.now(), - message: 'HandshakeException' - ) - }; - } catch (e) { - return { - 'result': 'error', - 'message': e.toString(), - 'log': AppLog( - type: 'check_latest_release_github', - dateTime: DateTime.now(), - message: e.toString() - ) - }; - } - } - Future requestUpdateServer() async { final result = await apiRequest( urlPath: '/update', @@ -2333,146 +2260,4 @@ class ApiClient { return result; } } -} - -Future getReleasesGitHub() async { - try { - HttpClient httpClient = HttpClient(); - HttpClientRequest request = await httpClient.getUrl(Uri.parse(Urls.getReleasesGitHub)); - HttpClientResponse response = await request.close(); - String reply = await response.transform(utf8.decoder).join(); - httpClient.close(); - if (response.statusCode == 200) { - return { - 'result': 'success', - 'hasResponse': true, - 'error': false, - 'statusCode': response.statusCode, - 'body': List.from(jsonDecode(reply).map((entry) => GitHubRelease.fromJson(entry))) - }; - } - else { - return { - 'result': 'error', - 'log': AppLog( - type: 'update_encryption_settings', - dateTime: DateTime.now(), - message: 'error_code_not_expected', - statusCode: response.statusCode.toString(), - resBody: reply, - ) - }; - } - } on SocketException { - return { - 'result': 'no_connection', - 'message': 'SocketException', - 'log': AppLog( - type: 'check_latest_release_github', - dateTime: DateTime.now(), - message: 'SocketException' - ) - }; - } on TimeoutException { - return { - 'result': 'no_connection', - 'message': 'TimeoutException', - 'log': AppLog( - type: 'check_latest_release_github', - dateTime: DateTime.now(), - message: 'TimeoutException' - ) - }; - } on HandshakeException { - return { - 'result': 'ssl_error', - 'message': 'HandshakeException', - 'log': AppLog( - type: 'check_latest_release_github', - dateTime: DateTime.now(), - message: 'HandshakeException' - ) - }; - } catch (e) { - return { - 'result': 'error', - 'message': e.toString(), - 'log': AppLog( - type: 'check_latest_release_github', - dateTime: DateTime.now(), - message: e.toString() - ) - }; - } -} - -Future getLatestReleaseGitHub() async { - try { - HttpClient httpClient = HttpClient(); - HttpClientRequest request = await httpClient.getUrl(Uri.parse(Urls.getLatestReleaseGitHub)); - HttpClientResponse response = await request.close(); - String reply = await response.transform(utf8.decoder).join(); - httpClient.close(); - if (response.statusCode == 200) { - return { - 'result': 'success', - 'hasResponse': true, - 'error': false, - 'statusCode': response.statusCode, - 'body': GitHubRelease.fromJson(jsonDecode(reply)) - }; - } - else { - return { - 'result': 'error', - 'log': AppLog( - type: 'update_encryption_settings', - dateTime: DateTime.now(), - message: 'error_code_not_expected', - statusCode: response.statusCode.toString(), - resBody: reply, - ) - }; - } - } on SocketException { - return { - 'result': 'no_connection', - 'message': 'SocketException', - 'log': AppLog( - type: 'check_latest_release_github', - dateTime: DateTime.now(), - message: 'SocketException' - ) - }; - } on TimeoutException { - return { - 'result': 'no_connection', - 'message': 'TimeoutException', - 'log': AppLog( - type: 'check_latest_release_github', - dateTime: DateTime.now(), - message: 'TimeoutException' - ) - }; - } on HandshakeException { - return { - 'result': 'ssl_error', - 'message': 'HandshakeException', - 'log': AppLog( - type: 'check_latest_release_github', - dateTime: DateTime.now(), - message: 'HandshakeException' - ) - }; - } catch (e) { - return { - 'result': 'error', - 'message': e.toString(), - 'log': AppLog( - type: 'check_latest_release_github', - dateTime: DateTime.now(), - message: e.toString() - ) - }; - } } \ No newline at end of file diff --git a/lib/widgets/add_server/add_server_modal.dart b/lib/widgets/add_server/add_server_modal.dart index 511d367..d237939 100644 --- a/lib/widgets/add_server/add_server_modal.dart +++ b/lib/widgets/add_server/add_server_modal.dart @@ -12,6 +12,7 @@ import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/services/api_client.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/constants/urls.dart'; import 'package:adguard_home_manager/functions/open_url.dart'; @@ -191,10 +192,11 @@ class _AddServerModalState extends State { statusProvider.setServerStatusLoad(LoadStatus.loading); final ApiClient apiClient = ApiClient(server: serverObj); final serverStatus = await apiClient.getServerStatus(); + final ApiClientV2 apiClient2 = ApiClientV2(server: serverObj); + final serverStatus2 = await apiClient2.getServerStatus(); // If something goes wrong when fetching server status - if (serverStatus['result'] != 'success') { - appConfigProvider.addLog(serverStatus['log']); + if (serverStatus2.successful == false) { statusProvider.setServerStatusLoad(LoadStatus.error); Navigator.pop(context); return; diff --git a/lib/widgets/servers_list/servers_list_item.dart b/lib/widgets/servers_list/servers_list_item.dart index 280ae7c..492a5e8 100644 --- a/lib/widgets/servers_list/servers_list_item.dart +++ b/lib/widgets/servers_list/servers_list_item.dart @@ -8,6 +8,8 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; import 'package:adguard_home_manager/widgets/servers_list/delete_modal.dart'; +import 'package:adguard_home_manager/models/server_status.dart'; +import 'package:adguard_home_manager/services/api_client.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/constants/enums.dart'; @@ -104,23 +106,21 @@ class _ServersListItemState extends State with SingleTickerProv if (result['result'] == 'success') { final ApiClient apiClient = ApiClient(server: server); serversProvider.setApiClient(apiClient); + final ApiClientV2 apiClient2 = ApiClientV2(server: server); + serversProvider.setApiClient2(apiClient2); serversProvider.setSelectedServer(server); statusProvider.setServerStatusLoad(LoadStatus.loading); - final serverStatus = await apiClient.getServerStatus(); - if (serverStatus['result'] == 'success') { + final serverStatus = await apiClient2.getServerStatus(); + if (serverStatus.successful == true) { statusProvider.setServerStatusData( - data: serverStatus['data'] + data: serverStatus.content as ServerStatus ); serversProvider.checkServerUpdatesAvailable( server: server, ); statusProvider.setServerStatusLoad(LoadStatus.loaded); } - else { - appConfigProvider.addLog(serverStatus['log']); - statusProvider.setServerStatusLoad(LoadStatus.error); - } process.close(); } diff --git a/lib/widgets/servers_list/servers_tile_item.dart b/lib/widgets/servers_list/servers_tile_item.dart index 8407526..0422368 100644 --- a/lib/widgets/servers_list/servers_tile_item.dart +++ b/lib/widgets/servers_list/servers_tile_item.dart @@ -7,6 +7,8 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; import 'package:adguard_home_manager/widgets/servers_list/delete_modal.dart'; +import 'package:adguard_home_manager/models/server_status.dart'; +import 'package:adguard_home_manager/services/api_client.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/constants/enums.dart'; @@ -73,13 +75,15 @@ class _ServersTileItemState extends State with SingleTickerProv if (result['result'] == 'success') { final ApiClient apiClient = ApiClient(server: server); serversProvider.setApiClient(apiClient); + final ApiClientV2 apiClient2 = ApiClientV2(server: server); + serversProvider.setApiClient2(apiClient2); serversProvider.setSelectedServer(server); statusProvider.setServerStatusLoad(LoadStatus.loading); - final serverStatus = await apiClient.getServerStatus(); - if (serverStatus['result'] == 'success') { + final serverStatus = await apiClient2.getServerStatus(); + if (serverStatus.successful == true) { statusProvider.setServerStatusData( - data: serverStatus['data'] + data: serverStatus.content as ServerStatus ); serversProvider.checkServerUpdatesAvailable( server: server, @@ -87,7 +91,6 @@ class _ServersTileItemState extends State with SingleTickerProv statusProvider.setServerStatusLoad(LoadStatus.loaded); } else { - appConfigProvider.addLog(serverStatus['log']); statusProvider.setServerStatusLoad(LoadStatus.error); } From 6acc5105839ad8c8b0a00baa49ae6f7a6ae9a20d Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 19 Nov 2023 22:53:24 +0100 Subject: [PATCH 080/177] Removed unused imports --- lib/screens/logs/logs_list_appbar.dart | 1 - lib/services/http_requests.dart | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/screens/logs/logs_list_appbar.dart b/lib/screens/logs/logs_list_appbar.dart index 7b99435..d2f0ec9 100644 --- a/lib/screens/logs/logs_list_appbar.dart +++ b/lib/screens/logs/logs_list_appbar.dart @@ -11,7 +11,6 @@ import 'package:adguard_home_manager/screens/logs/configuration/logs_config_moda import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/functions/compare_versions.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/models/applied_filters.dart'; diff --git a/lib/services/http_requests.dart b/lib/services/http_requests.dart index 8063b4f..2d001f2 100644 --- a/lib/services/http_requests.dart +++ b/lib/services/http_requests.dart @@ -4,12 +4,13 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:sentry_flutter/sentry_flutter.dart'; + import 'package:adguard_home_manager/models/blocked_services.dart'; import 'package:adguard_home_manager/models/dhcp.dart'; import 'package:adguard_home_manager/models/dns_info.dart'; import 'package:adguard_home_manager/models/encryption.dart'; import 'package:adguard_home_manager/models/filtering.dart'; -import 'package:adguard_home_manager/models/github_release.dart'; import 'package:adguard_home_manager/models/logs.dart'; import 'package:adguard_home_manager/models/filtering_status.dart'; import 'package:adguard_home_manager/models/app_log.dart'; @@ -19,8 +20,6 @@ import 'package:adguard_home_manager/models/server_status.dart'; import 'package:adguard_home_manager/models/clients.dart'; import 'package:adguard_home_manager/models/clients_allowed_blocked.dart'; import 'package:adguard_home_manager/models/server.dart'; -import 'package:adguard_home_manager/constants/urls.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; Future> apiRequest({ From d38b895076eac0e646f7982f87dc15ccede8d5d9 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 20 Nov 2023 01:51:17 +0100 Subject: [PATCH 081/177] Added warning message https connections --- lib/widgets/add_server/add_server_modal.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/widgets/add_server/add_server_modal.dart b/lib/widgets/add_server/add_server_modal.dart index d237939..509ed95 100644 --- a/lib/widgets/add_server/add_server_modal.dart +++ b/lib/widgets/add_server/add_server_modal.dart @@ -395,6 +395,21 @@ class _AddServerModalState extends State { horizontal: 24, ), ), + if (connectionType == ConnectionType.https) Card( + margin: const EdgeInsets.only( + top: 16, left: 24, right: 24 + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + const Icon(Icons.info_rounded), + const SizedBox(width: 16), + Flexible(child: Text(AppLocalizations.of(context)!.sslWarning)) + ], + ), + ), + ), const SizedBox(height: 30), FormTextField( label: AppLocalizations.of(context)!.ipDomain, From e5528c0d2c67cf13ed8aa70bdeabb58f511be4e1 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 20 Nov 2023 01:51:35 +0100 Subject: [PATCH 082/177] Changed auth functions --- lib/l10n/app_en.arb | 3 +- lib/l10n/app_es.arb | 3 +- lib/services/api_client.dart | 13 ++++ lib/services/auth.dart | 82 ++++++++++++++++++++ lib/widgets/add_server/add_server_modal.dart | 69 +++++++--------- 5 files changed, 129 insertions(+), 41 deletions(-) create mode 100644 lib/services/auth.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2fc732c..cc45a00 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -681,5 +681,6 @@ "failedElements": "Failed elements", "processingLists": "Processing lists...", "enableDisableResult": "Enable or disable result", - "selectedListsEnabledDisabledSuccessfully": "All selected lists have been enabled or disabled successfully" + "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." } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 6f2d8d6..9676508 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -681,5 +681,6 @@ "failedElements": "Elementos fallidos", "processingLists": "Procesando listas...", "enableDisableResult": "Resultado de activar o desactivar", - "selectedListsEnabledDisabledSuccessfully": "Todas las listas seleccionadas se han activado o desactivado correctamente." + "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." } \ No newline at end of file diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index 692739c..a957fc3 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -36,6 +36,19 @@ class ApiClientV2 { required this.server }); + Future getServerVersion() async { + final result = await HttpRequestClient.get(urlPath: '/status', server: server); + if (result.successful == true) { + return ApiResponse( + successful: true, + content: jsonDecode(result.body!)['version'] + ); + } + else { + return const ApiResponse(successful: false); + } + } + Future getServerStatus() async { final results = await Future.wait([ HttpRequestClient.get(urlPath: "/stats", server: server), diff --git a/lib/services/auth.dart b/lib/services/auth.dart new file mode 100644 index 0000000..a450348 --- /dev/null +++ b/lib/services/auth.dart @@ -0,0 +1,82 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:adguard_home_manager/classes/http_client.dart'; +import 'package:adguard_home_manager/models/server.dart'; + +enum AuthStatus { + success, + invalidCredentials, + manyAttepts, + serverError, + socketException, + timeoutException, + handshakeException, + unknown +} + +class ServerAuth { + static Future login(Server server) async { + try { + final body = { + "name": server.user, + "password": server.password + }; + final connectionString = "${server.connectionMethod}://${server.domain}${server.port != null ? ':${server.port}' : ""}${server.path ?? ""}/control/login"; + HttpClient httpClient = HttpClient(); + HttpClientRequest request = await httpClient.postUrl(Uri.parse(connectionString)); + request.headers.set('content-type', 'application/json'); + request.headers.contentLength = utf8.encode(jsonEncode(body)).length; + request.add(utf8.encode(json.encode(body))); + HttpClientResponse response = await request.close().timeout(const Duration(seconds: 10)); + httpClient.close(); + if (response.statusCode == 200) { + return AuthStatus.success; + } + else if (response.statusCode == 400 || response.statusCode == 401 || response.statusCode == 403) { + return AuthStatus.invalidCredentials; + } + else if (response.statusCode == 429) { + return AuthStatus.manyAttepts; + } + else if (response.statusCode == 500) { + return AuthStatus.serverError; + } + else { + return AuthStatus.unknown; + } + } on SocketException { + return AuthStatus.socketException; + } on TimeoutException { + return AuthStatus.timeoutException; + } on HandshakeException { + return AuthStatus.handshakeException; + } catch (e) { + return AuthStatus.unknown; + } + } + + static Future loginHA(Server server) async { + try { + final result = await HttpRequestClient.get(urlPath: "/status", server: server); + if (result.successful) { + return AuthStatus.success; + } + else if (result.statusCode == 401 || result.statusCode == 403) { + return AuthStatus.invalidCredentials; + } + else { + return AuthStatus.unknown; + } + } on SocketException { + return AuthStatus.socketException; + } on TimeoutException { + return AuthStatus.timeoutException; + } on HandshakeException { + return AuthStatus.handshakeException; + } catch (e) { + return AuthStatus.unknown; + } + } +} \ No newline at end of file diff --git a/lib/widgets/add_server/add_server_modal.dart b/lib/widgets/add_server/add_server_modal.dart index 509ed95..049bf7f 100644 --- a/lib/widgets/add_server/add_server_modal.dart +++ b/lib/widgets/add_server/add_server_modal.dart @@ -1,5 +1,4 @@ // ignore_for_file: use_build_context_synchronously - import 'package:provider/provider.dart'; import 'package:flutter/material.dart'; import 'package:segmented_button_slide/segmented_button_slide.dart'; @@ -11,6 +10,8 @@ import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; +import 'package:adguard_home_manager/models/server_status.dart'; +import 'package:adguard_home_manager/services/auth.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/services/api_client.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; @@ -19,7 +20,6 @@ import 'package:adguard_home_manager/functions/open_url.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/functions/base64.dart'; -import 'package:adguard_home_manager/services/http_requests.dart'; import 'package:adguard_home_manager/models/app_log.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/models/server.dart'; @@ -32,11 +32,11 @@ class AddServerModal extends StatefulWidget { final void Function(String version) onUnsupportedVersion; const AddServerModal({ - Key? key, + super.key, this.server, required this.fullScreen, required this.onUnsupportedVersion - }) : super(key: key); + }); @override State createState() => _AddServerModalState(); @@ -119,11 +119,11 @@ class _AddServerModalState extends State { )); } - String getErrorMessage(String message) { - if (message == 'invalid_username_password') return AppLocalizations.of(context)!.invalidUsernamePassword; - if (message == 'many_attempts') return AppLocalizations.of(context)!.tooManyAttempts; - if (message == 'no_connection') return AppLocalizations.of(context)!.cantReachServer; - if (message == 'server_error') return AppLocalizations.of(context)!.serverError; + String getErrorMessage(AuthStatus status) { + if (status == AuthStatus.invalidCredentials) return AppLocalizations.of(context)!.invalidUsernamePassword; + if (status == AuthStatus.manyAttepts) return AppLocalizations.of(context)!.tooManyAttempts; + if (status == AuthStatus.socketException || status == AuthStatus.timeoutException) return AppLocalizations.of(context)!.cantReachServer; + if (status == AuthStatus.serverError) return AppLocalizations.of(context)!.serverError; return AppLocalizations.of(context)!.unknownError; } @@ -146,17 +146,16 @@ class _AddServerModalState extends State { ); final result = homeAssistant == true - ? await loginHA(serverObj) - : await login(serverObj); + ? await ServerAuth.loginHA(serverObj) + : await ServerAuth.login(serverObj); // If something goes wrong with the connection - if (result['result'] != 'success') { + if (result != AuthStatus.success) { cancelConnecting(); - appConfigProvider.addLog(result['log']); if (mounted) { showSnacbkar( appConfigProvider: appConfigProvider, - label: getErrorMessage(result['result']), + label: getErrorMessage(result), color: Colors.red ); } @@ -172,13 +171,6 @@ class _AddServerModalState extends State { // If something goes wrong when saving the connection on the db if (serverCreated != null) { if (mounted) setState(() => isConnecting = false); - appConfigProvider.addLog( - AppLog( - type: 'save_connection_db', - dateTime: DateTime.now(), - message: serverCreated.toString() - ) - ); if (mounted) { showSnacbkar( appConfigProvider: appConfigProvider, @@ -190,27 +182,27 @@ class _AddServerModalState extends State { } statusProvider.setServerStatusLoad(LoadStatus.loading); - final ApiClient apiClient = ApiClient(server: serverObj); - final serverStatus = await apiClient.getServerStatus(); final ApiClientV2 apiClient2 = ApiClientV2(server: serverObj); - final serverStatus2 = await apiClient2.getServerStatus(); + final serverStatus = await apiClient2.getServerStatus(); // If something goes wrong when fetching server status - if (serverStatus2.successful == false) { + if (serverStatus.successful == false) { statusProvider.setServerStatusLoad(LoadStatus.error); Navigator.pop(context); return; } + final status = serverStatus.content as ServerStatus; + // If everything is successful statusProvider.setServerStatusData( - data: serverStatus['data'] + data: status ); - serversProvider.setApiClient(apiClient); + serversProvider.setApiClient2(apiClient2); statusProvider.setServerStatusLoad(LoadStatus.loaded); - if (serverStatus['data'].serverVersion.contains('a') || serverStatus['data'].serverVersion.contains('b')) { + if (status.serverVersion.contains('a') || status.serverVersion.contains('b')) { Navigator.pop(context); - widget.onUnsupportedVersion(serverStatus['data'].serverVersion); + widget.onUnsupportedVersion(status.serverVersion); } else { Navigator.pop(context); @@ -236,17 +228,16 @@ class _AddServerModalState extends State { ); final result = homeAssistant == true - ? await loginHA(serverObj) - : await login(serverObj); + ? await ServerAuth.loginHA(serverObj) + : await ServerAuth.login(serverObj); // If something goes wrong with the connection - if (result['result'] != 'success') { + if (result != AuthStatus.success) { cancelConnecting(); - appConfigProvider.addLog(result['log']); if (mounted) { showSnacbkar( appConfigProvider: appConfigProvider, - label: getErrorMessage(result['result']), + label: getErrorMessage(result), color: Colors.red ); } @@ -279,14 +270,14 @@ class _AddServerModalState extends State { } // If everything is successful - final ApiClient apiClient = ApiClient(server: serverObj); - final version = await apiClient.getServerVersion(); + final ApiClientV2 apiClient2 = ApiClientV2(server: serverObj); + final version = await apiClient2.getServerVersion(); if ( - version['result'] == 'success' && - (version['data'].contains('a') || version['data'].contains('b')) // alpha or beta + version.successful == true && + (version.content.contains('a') || version.content.contains('b')) // alpha or beta ) { Navigator.pop(context); - widget.onUnsupportedVersion(version['data']); + widget.onUnsupportedVersion(version.content); } else { Navigator.pop(context); From e08404b140429aa6e335f83bb01abe3613ab0992 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 20 Nov 2023 02:17:22 +0100 Subject: [PATCH 083/177] Check server version --- lib/config/minimum_server_version.dart | 4 + lib/l10n/app_en.arb | 6 +- lib/l10n/app_es.arb | 6 +- lib/widgets/add_server/add_server_modal.dart | 79 +++++++++++++++---- .../add_server/unsupported_version_modal.dart | 76 ++++++++++++++++++ 5 files changed, 152 insertions(+), 19 deletions(-) create mode 100644 lib/config/minimum_server_version.dart create mode 100644 lib/widgets/add_server/unsupported_version_modal.dart diff --git a/lib/config/minimum_server_version.dart b/lib/config/minimum_server_version.dart new file mode 100644 index 0000000..f554cb9 --- /dev/null +++ b/lib/config/minimum_server_version.dart @@ -0,0 +1,4 @@ +class MinimumServerVersion { + static const String stable = "v0.107.28"; + static const String beta = "v0.108.0-b.33"; +} \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index cc45a00..7c126ba 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -682,5 +682,9 @@ "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." + "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}" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 9676508..9033460 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -682,5 +682,9 @@ "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." + "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}" } \ No newline at end of file diff --git a/lib/widgets/add_server/add_server_modal.dart b/lib/widgets/add_server/add_server_modal.dart index 049bf7f..59e8a55 100644 --- a/lib/widgets/add_server/add_server_modal.dart +++ b/lib/widgets/add_server/add_server_modal.dart @@ -5,12 +5,15 @@ import 'package:segmented_button_slide/segmented_button_slide.dart'; import 'package:uuid/uuid.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:adguard_home_manager/widgets/add_server/unsupported_version_modal.dart'; import 'package:adguard_home_manager/widgets/add_server/form_text_field.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; +import 'package:adguard_home_manager/config/minimum_server_version.dart'; import 'package:adguard_home_manager/models/server_status.dart'; +import 'package:adguard_home_manager/functions/compare_versions.dart'; import 'package:adguard_home_manager/services/auth.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/services/api_client.dart'; @@ -166,6 +169,36 @@ class _AddServerModalState extends State { serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!); } + statusProvider.setServerStatusLoad(LoadStatus.loading); + final ApiClientV2 apiClient2 = ApiClientV2(server: serverObj); + final serverStatus = await apiClient2.getServerStatus(); + + // If something goes wrong when fetching server status + if (serverStatus.successful == false) { + statusProvider.setServerStatusLoad(LoadStatus.error); + Navigator.pop(context); + return; + } + + final status = serverStatus.content as ServerStatus; + + // Check if ths server version is compatible + final validVersion = serverVersionIsAhead( + currentVersion: status.serverVersion, + referenceVersion: MinimumServerVersion.stable, + referenceVersionBeta: MinimumServerVersion.beta + ); + if (validVersion == false) { + showDialog( + context: context, + builder: (ctx) => UnsupportedVersionModal( + serverVersion: status.serverVersion, + onClose: () => Navigator.pop(context) + ) + ); + return; + } + final serverCreated = await serversProvider.createServer(serverObj); // If something goes wrong when saving the connection on the db @@ -181,19 +214,6 @@ class _AddServerModalState extends State { return; } - statusProvider.setServerStatusLoad(LoadStatus.loading); - final ApiClientV2 apiClient2 = ApiClientV2(server: serverObj); - final serverStatus = await apiClient2.getServerStatus(); - - // If something goes wrong when fetching server status - if (serverStatus.successful == false) { - statusProvider.setServerStatusLoad(LoadStatus.error); - Navigator.pop(context); - return; - } - - final status = serverStatus.content as ServerStatus; - // If everything is successful statusProvider.setServerStatusData( data: status @@ -247,6 +267,31 @@ class _AddServerModalState extends State { if (serverObj.user != null && serverObj.password != null) { serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!); } + + final ApiClientV2 apiClient2 = ApiClientV2(server: serverObj); + final version = await apiClient2.getServerVersion(); + if (version.successful == false) { + if (mounted) setState(() => isConnecting = false); + return; + } + + // Check if ths server version is compatible + final validVersion = serverVersionIsAhead( + currentVersion: version.content, + referenceVersion: MinimumServerVersion.stable, + referenceVersionBeta: MinimumServerVersion.beta + ); + if (validVersion == false) { + showDialog( + context: context, + builder: (ctx) => UnsupportedVersionModal( + serverVersion: version.content, + onClose: () => Navigator.pop(context) + ) + ); + return; + } + final serverSaved = await serversProvider.editServer(serverObj); // If something goes wrong when saving the connection on the db @@ -270,8 +315,6 @@ class _AddServerModalState extends State { } // If everything is successful - final ApiClientV2 apiClient2 = ApiClientV2(server: serverObj); - final version = await apiClient2.getServerVersion(); if ( version.successful == true && (version.content.contains('a') || version.content.contains('b')) // alpha or beta @@ -499,7 +542,7 @@ class _AddServerModalState extends State { leading: CloseButton( onPressed: () => Navigator.pop(context), ), - title: widget.server != null + title: widget.server == null ? Text(AppLocalizations.of(context)!.createConnection) : Text(AppLocalizations.of(context)!.editConnection), actions: [ @@ -532,7 +575,9 @@ class _AddServerModalState extends State { ), const SizedBox(width: 8), Text( - AppLocalizations.of(context)!.createConnection, + widget.server == null + ? AppLocalizations.of(context)!.createConnection + : AppLocalizations.of(context)!.editConnection, style: const TextStyle( fontSize: 20 ), diff --git a/lib/widgets/add_server/unsupported_version_modal.dart b/lib/widgets/add_server/unsupported_version_modal.dart new file mode 100644 index 0000000..3340aad --- /dev/null +++ b/lib/widgets/add_server/unsupported_version_modal.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/config/minimum_server_version.dart'; + +class UnsupportedVersionModal extends StatelessWidget { + final String serverVersion; + final void Function() onClose; + + const UnsupportedVersionModal({ + super.key, + required this.serverVersion, + required this.onClose, + }); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Column( + children: [ + Icon( + Icons.error_rounded, + size: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.unsupportedServerVersion, + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + AppLocalizations.of(context)!.unsupportedServerVersionMessage, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.yourVersion(serverVersion), + style: const TextStyle( + fontStyle: FontStyle.italic + ), + ), + const SizedBox(height: 4), + Text( + AppLocalizations.of(context)!.minimumRequiredVersion( + serverVersion.contains("b") + ? MinimumServerVersion.beta + : MinimumServerVersion.stable + ), + style: const TextStyle( + fontStyle: FontStyle.italic + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + onClose(); + }, + child: Text(AppLocalizations.of(context)!.close) + ), + ], + ); + } +} \ No newline at end of file From 93636526220d9ea6420c41ae1af1365cacc43adc Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 20 Nov 2023 02:26:53 +0100 Subject: [PATCH 084/177] Removed legacy code --- lib/models/clients.dart | 4 - lib/providers/clients_provider.dart | 16 +-- .../clients/client/added_client_tile.dart | 59 +++------ lib/screens/clients/client/client_form.dart | 25 +--- lib/screens/clients/client/client_screen.dart | 20 +-- lib/screens/clients/search_clients.dart | 26 ++-- .../home/management_modal/main_switch.dart | 115 +++++++----------- .../dns_rewrites/dns_rewrite_modal.dart | 12 -- lib/screens/settings/settings.dart | 6 +- 9 files changed, 70 insertions(+), 213 deletions(-) diff --git a/lib/models/clients.dart b/lib/models/clients.dart index 4d8e7db..1472a0f 100644 --- a/lib/models/clients.dart +++ b/lib/models/clients.dart @@ -84,7 +84,6 @@ class Client { final bool filteringEnabled; final bool parentalEnabled; final bool safebrowsingEnabled; - final bool? safesearchEnabled; final bool useGlobalBlockedServices; final bool useGlobalSettings; final SafeSearch? safeSearch; @@ -98,7 +97,6 @@ class Client { required this.filteringEnabled, required this.parentalEnabled, required this.safebrowsingEnabled, - required this.safesearchEnabled, required this.useGlobalBlockedServices, required this.useGlobalSettings, required this.safeSearch, @@ -113,7 +111,6 @@ 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 @@ -130,7 +127,6 @@ 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, diff --git a/lib/providers/clients_provider.dart b/lib/providers/clients_provider.dart index ae9cb28..a739c29 100644 --- a/lib/providers/clients_provider.dart +++ b/lib/providers/clients_provider.dart @@ -142,13 +142,7 @@ class ClientsProvider with ChangeNotifier { 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') } ); @@ -175,13 +169,7 @@ class ClientsProvider with ChangeNotifier { Future addClient(Client client) async { final result = await _serversProvider!.apiClient2!.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') + data: removePropFromMap(client.toJson(), 'safe_search') ); if (result.successful == true) { diff --git a/lib/screens/clients/client/added_client_tile.dart b/lib/screens/clients/client/added_client_tile.dart index aa3c31e..53a481a 100644 --- a/lib/screens/clients/client/added_client_tile.dart +++ b/lib/screens/clients/client/added_client_tile.dart @@ -5,8 +5,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; -import 'package:adguard_home_manager/providers/status_provider.dart'; -import 'package:adguard_home_manager/functions/compare_versions.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'; @@ -21,7 +19,7 @@ class AddedClientTile extends StatelessWidget { final bool? splitView; const AddedClientTile({ - Key? key, + super.key, required this.client, required this.onTap, required this.onLongPress, @@ -29,11 +27,10 @@ class AddedClientTile extends StatelessWidget { required this.onDelete, this.selectedClient, required this.splitView, - }) : super(key: key); + }); @override Widget build(BuildContext context) { - final statusProvider = Provider.of(context); final appConfigProvider = Provider.of(context); if (splitView == true) { @@ -146,25 +143,13 @@ class AddedClientTile extends StatelessWidget { Icon( Icons.search_rounded, size: 19, - color: serverVersionIsAhead( - currentVersion: statusProvider.serverStatus!.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: client.safeSearch != null && client.safeSearch!.enabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red ) ], ) @@ -260,25 +245,13 @@ class AddedClientTile extends StatelessWidget { Icon( Icons.search_rounded, size: 19, - color: serverVersionIsAhead( - currentVersion: statusProvider.serverStatus!.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: client.safeSearch != null && client.safeSearch!.enabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red ) ], ) diff --git a/lib/screens/clients/client/client_form.dart b/lib/screens/clients/client/client_form.dart index ff4db9a..5d54b86 100644 --- a/lib/screens/clients/client/client_form.dart +++ b/lib/screens/clients/client/client_form.dart @@ -1,7 +1,6 @@ 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/blocked_services_section.dart'; @@ -15,10 +14,8 @@ import 'package:adguard_home_manager/screens/clients/client/upstream_servers_sec import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; -import 'package:adguard_home_manager/functions/compare_versions.dart'; import 'package:adguard_home_manager/models/clients.dart'; import 'package:adguard_home_manager/models/safe_search.dart'; -import 'package:adguard_home_manager/providers/status_provider.dart'; class ClientForm extends StatelessWidget { final bool isFullScreen; @@ -82,8 +79,6 @@ class ClientForm extends StatelessWidget { @override Widget build(BuildContext context) { - final statusProvider = Provider.of(context); - return ListView( padding: const EdgeInsets.only(top: 0), children: [ @@ -195,13 +190,7 @@ class ClientForm extends StatelessWidget { onChange: (value) => updateEnableParentalControl(value), useGlobalSettingsFiltering: useGlobalSettingsFiltering, ), - if ( - serverVersionIsAhead( - currentVersion: statusProvider.serverStatus!.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == true - ) CustomListTile( + CustomListTile( title: AppLocalizations.of(context)!.safeSearch, padding: const EdgeInsets.symmetric( horizontal: 42, @@ -226,18 +215,6 @@ class ClientForm extends StatelessWidget { ) : null, ), - if ( - serverVersionIsAhead( - currentVersion: statusProvider.serverStatus!.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == false - ) SettingsTile( - label: AppLocalizations.of(context)!.enableSafeSearch, - value: enableSafeSearch, - onChange: (value) => updateEnableSafeSearch(value), - useGlobalSettingsFiltering: useGlobalSettingsFiltering, - ), SectionLabel( label: AppLocalizations.of(context)!.blockedServices, padding: const EdgeInsets.all(24), diff --git a/lib/screens/clients/client/client_screen.dart b/lib/screens/clients/client/client_screen.dart index 243981c..34fb88b 100644 --- a/lib/screens/clients/client/client_screen.dart +++ b/lib/screens/clients/client/client_screen.dart @@ -6,10 +6,8 @@ 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/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 ControllerListItem { @@ -75,8 +73,6 @@ class _ClientScreenState extends State { List upstreamServers = []; - bool version = false; - void enableDisableGlobalSettingsFiltering() { if (useGlobalSettingsFiltering == true) { setState(() { @@ -104,12 +100,6 @@ class _ClientScreenState extends State { @override void initState() { - version = serverVersionIsAhead( - currentVersion: Provider.of(context, listen: false).serverStatus!.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ); - if (widget.client != null) { validValues = true; @@ -123,12 +113,7 @@ class _ClientScreenState extends State { 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; - } + safeSearch = widget.client!.safeSearch; useGlobalSettingsServices = widget.client!.useGlobalBlockedServices; blockedServices = widget.client!.blockedServices; upstreamServers = widget.client!.upstreams.map((e) => ControllerListItem( @@ -151,8 +136,7 @@ class _ClientScreenState extends State { filteringEnabled: enableFiltering ?? false, parentalEnabled: enableParentalControl ?? false, safebrowsingEnabled: enableSafeBrowsing ?? false, - safesearchEnabled: version == false ? enableSafeSearch : null, - safeSearch: version == true ? safeSearch : null, + safeSearch: safeSearch, useGlobalBlockedServices: useGlobalSettingsServices, blockedServices: blockedServices, upstreams: List.from(upstreamServers.map((e) => e.controller.text)), diff --git a/lib/screens/clients/search_clients.dart b/lib/screens/clients/search_clients.dart index 99d430c..a9c2cda 100644 --- a/lib/screens/clients/search_clients.dart +++ b/lib/screens/clients/search_clients.dart @@ -290,25 +290,13 @@ class _SearchClientsState extends State { 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 - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red, + 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 ) ], ) diff --git a/lib/screens/home/management_modal/main_switch.dart b/lib/screens/home/management_modal/main_switch.dart index 6c7b2ae..b2ae9ea 100644 --- a/lib/screens/home/management_modal/main_switch.dart +++ b/lib/screens/home/management_modal/main_switch.dart @@ -29,88 +29,55 @@ class MainSwitch extends StatelessWidget { 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, + 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), - 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( + 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, - ), - expanded: Column( - children: [ - _TopRow( - legacyMode: false, - expandableController: expandableController, - updateBlocking: updateBlocking, - animation: animation, - ), - _BottomRow( - disableWithCountdown: disableWithCountdown, - ), - 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, - expandableController: expandableController, - updateBlocking: updateBlocking, - animation: animation, + ), + _BottomRow( + disableWithCountdown: disableWithCountdown, + ), + const SizedBox(height: 8) + ], ) ), ), - ) + ), + ) + ) ); } } diff --git a/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart b/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart index b43c168..74d50d8 100644 --- a/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart +++ b/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart @@ -181,11 +181,6 @@ class _AddDnsRewriteModalState extends State { TextButton( onPressed: validData == true ? () { - if (serverVersionIsAhead( - currentVersion: statusProvider.serverStatus!.serverVersion, - referenceVersion: '0.107.33', - referenceVersionBeta: '0.108.0-b.39' - )) { Navigator.pop(context); widget.onConfirm( RewriteRules( @@ -195,13 +190,6 @@ class _AddDnsRewriteModalState extends State { widget.rule ); } - else { - showDialog( - context: context, - builder: (context) => const ServerVersionNeeded(version: 'v0.107.33') - ); - } - } : null, child: Text( AppLocalizations.of(context)!.confirm, diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index 6a67d28..ed3dd26 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -170,11 +170,7 @@ class _SettingsWidgetState extends State { serversProvider.apiClient != null ) ...[ SectionLabel(label: AppLocalizations.of(context)!.serverSettings), - if (serverVersionIsAhead( - currentVersion: statusProvider.serverStatus!.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == true) settingsTile( + settingsTile( icon: Icons.search_rounded, title: AppLocalizations.of(context)!.safeSearch, subtitle: AppLocalizations.of(context)!.safeSearchSettings, From 5e6708366125ff25e701535925222f9122ff62ba Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 20 Nov 2023 02:31:07 +0100 Subject: [PATCH 085/177] Removed legacy code --- lib/screens/clients/search_clients.dart | 3 +- .../home/management_modal/main_switch.dart | 1 - .../dns_rewrites/dns_rewrite_modal.dart | 336 +++++++++--------- lib/screens/settings/settings.dart | 1 - 4 files changed, 176 insertions(+), 165 deletions(-) diff --git a/lib/screens/clients/search_clients.dart b/lib/screens/clients/search_clients.dart index a9c2cda..bbc330c 100644 --- a/lib/screens/clients/search_clients.dart +++ b/lib/screens/clients/search_clients.dart @@ -13,7 +13,6 @@ import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.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'; @@ -21,7 +20,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 createState() => _SearchClientsState(); diff --git a/lib/screens/home/management_modal/main_switch.dart b/lib/screens/home/management_modal/main_switch.dart index b2ae9ea..d4ce5ed 100644 --- a/lib/screens/home/management_modal/main_switch.dart +++ b/lib/screens/home/management_modal/main_switch.dart @@ -6,7 +6,6 @@ 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/functions/compare_versions.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; class MainSwitch extends StatelessWidget { diff --git a/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart b/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart index 74d50d8..fcd62d7 100644 --- a/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart +++ b/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart @@ -1,34 +1,78 @@ 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/settings/dns_rewrites/server_version_needed.dart'; - -import 'package:adguard_home_manager/providers/status_provider.dart'; -import 'package:adguard_home_manager/functions/compare_versions.dart'; import 'package:adguard_home_manager/models/rewrite_rules.dart'; -class DnsRewriteModal extends StatefulWidget { +class DnsRewriteModal extends StatelessWidget { final void Function(RewriteRules newRule, RewriteRules? previousRule) onConfirm; final bool dialog; final RewriteRules? rule; final void Function(RewriteRules) onDelete; const DnsRewriteModal({ - Key? key, + super.key, required this.onConfirm, required this.dialog, this.rule, required this.onDelete - }) : super(key: key); + }); @override - State createState() => _AddDnsRewriteModalState(); + Widget build(BuildContext context) { + if (dialog == true) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400 + ), + child: _Content( + onConfirm: onConfirm, + onDelete: onDelete, + rule: rule, + ) + ), + ); + } + 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( + onConfirm: onConfirm, + onDelete: onDelete, + rule: rule, + ) + ), + ); + } + } } -class _AddDnsRewriteModalState extends State { +class _Content extends StatefulWidget { + final void Function(RewriteRules newRule, RewriteRules? previousRule) onConfirm; + final RewriteRules? rule; + final void Function(RewriteRules) onDelete; + + const _Content({ + required this.onConfirm, + this.rule, + required this.onDelete + }); + + @override + State<_Content> createState() => _ContentState(); +} + +class _ContentState extends State<_Content> { final TextEditingController domainController = TextEditingController(); String? domainError; final TextEditingController answerController = TextEditingController(); @@ -71,169 +115,139 @@ class _AddDnsRewriteModalState extends State { @override Widget build(BuildContext context) { - final statusProvider = Provider.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( - widget.rule != null - ? Icons.edit - : Icons.add, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - const SizedBox(height: 16), - Text( + 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.rule != null - ? AppLocalizations.of(context)!.editRewriteRule - : AppLocalizations.of(context)!.addDnsRewrite, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface - ), + ? Icons.edit + : Icons.add, + size: 24, + color: Theme.of(context).listTileTheme.iconColor ), - const SizedBox(height: 16), - ], - ), - ], - ), - Padding( - padding: const EdgeInsets.only( - left: 24, right: 24, bottom: 12 - ), - 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, + const SizedBox(height: 16), + Text( + widget.rule != null + ? AppLocalizations.of(context)!.editRewriteRule + : AppLocalizations.of(context)!.addDnsRewrite, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), + ), + const SizedBox(height: 24), + ], + ), + ], + ), + Padding( + padding: const EdgeInsets.only( + left: 24, right: 24, bottom: 12 + ), + 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, ), ), - Padding( - padding: const EdgeInsets.only( - left: 24, right: 24, top: 12 + ), + Padding( + padding: const EdgeInsets.only( + left: 24, right: 24, top: 12 + ), + child: TextFormField( + controller: answerController, + onChanged: (_) => checkValidValues(), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.system_update_alt_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.answer, ), - child: TextFormField( - controller: answerController, - onChanged: (_) => checkValidValues(), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.system_update_alt_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.answer, + ), + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (widget.rule != null) TextButton( + onPressed: () { + Navigator.pop(context); + widget.onDelete( + RewriteRules( + domain: domainController.text, + answer: answerController.text + ) + ); + }, + child: Text(AppLocalizations.of(context)!.delete), + ), + if (widget.rule == null) const SizedBox(), + Row( + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel), + ), + const SizedBox(width: 20), + TextButton( + onPressed: validData == true + ? () { + Navigator.pop(context); + widget.onConfirm( + RewriteRules( + domain: domainController.text, + answer: answerController.text + ), + widget.rule + ); + } + : null, + child: Text( + AppLocalizations.of(context)!.confirm, + style: TextStyle( + color: validData == true + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurface.withOpacity(0.38) ), ), ), ], - ), - ), + ) + ], ), - Padding( - padding: const EdgeInsets.all(24), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (widget.rule != null) TextButton( - onPressed: () { - Navigator.pop(context); - widget.onDelete( - RewriteRules( - domain: domainController.text, - answer: answerController.text - ) - ); - }, - child: Text(AppLocalizations.of(context)!.delete), - ), - if (widget.rule == null) const SizedBox(), - Row( - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel), - ), - const SizedBox(width: 20), - TextButton( - onPressed: validData == true - ? () { - Navigator.pop(context); - widget.onConfirm( - RewriteRules( - domain: domainController.text, - answer: answerController.text - ), - widget.rule - ); - } - : null, - child: Text( - AppLocalizations.of(context)!.confirm, - style: TextStyle( - color: validData == true - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurface.withOpacity(0.38) - ), - ), - ), - ], - ) - ], - ), - ), - 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() - ), - ); - } + if (Platform.isIOS) const SizedBox(height: 16) + ], + ); } } \ No newline at end of file diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index ed3dd26..c3b1337 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -26,7 +26,6 @@ import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/constants/strings.dart'; import 'package:adguard_home_manager/functions/open_url.dart'; -import 'package:adguard_home_manager/functions/compare_versions.dart'; import 'package:adguard_home_manager/constants/urls.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; From dbb26093399506c06123a990c7061a3c3e5c1037 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 20 Nov 2023 02:31:47 +0100 Subject: [PATCH 086/177] Removed unused import --- lib/providers/clients_provider.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/providers/clients_provider.dart b/lib/providers/clients_provider.dart index a739c29..bfcf19d 100644 --- a/lib/providers/clients_provider.dart +++ b/lib/providers/clients_provider.dart @@ -2,7 +2,6 @@ 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'; @@ -13,11 +12,9 @@ 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; From 39cdcf648d6bf53e0fe5dce443a99a6bc49044ff Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 20 Nov 2023 14:22:01 +0100 Subject: [PATCH 087/177] Show server version message on app start --- lib/config/globals.dart | 3 ++- lib/main.dart | 12 +++++++----- lib/providers/servers_provider.dart | 2 +- lib/providers/status_provider.dart | 27 ++++++++++++++++++++++++++- lib/widgets/layout.dart | 4 ++-- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/lib/config/globals.dart b/lib/config/globals.dart index d21860b..a955a77 100644 --- a/lib/config/globals.dart +++ b/lib/config/globals.dart @@ -1,3 +1,4 @@ import 'package:flutter/material.dart'; -final GlobalKey scaffoldMessengerKey = GlobalKey(); \ No newline at end of file +final GlobalKey scaffoldMessengerKey = GlobalKey(); +final GlobalKey globalNavigatorKey = GlobalKey(); \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index b025caa..5b38138 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -235,13 +235,16 @@ class _MainState extends State
{ Locale('tr', '') ], scaffoldMessengerKey: scaffoldMessengerKey, + navigatorKey: globalNavigatorKey, builder: (context, child) { return CustomMenuBar( child: MediaQuery( data: MediaQuery.of(context).copyWith( - textScaleFactor: !(Platform.isAndroid || Platform.isIOS) - ? 0.9 - : 1.0 + textScaler: TextScaler.linear( + !(Platform.isAndroid || Platform.isIOS) + ? 0.9 + : 1.0 + ) ), child: child!, ), @@ -251,5 +254,4 @@ class _MainState extends State
{ ), ); } -} - +} \ No newline at end of file diff --git a/lib/providers/servers_provider.dart b/lib/providers/servers_provider.dart index 600c46e..addf617 100644 --- a/lib/providers/servers_provider.dart +++ b/lib/providers/servers_provider.dart @@ -60,7 +60,7 @@ class ServersProvider with ChangeNotifier { notifyListeners(); } - void setSelectedServer(Server server) { + void setSelectedServer(Server? server) { _selectedServer = server; notifyListeners(); } diff --git a/lib/providers/status_provider.dart b/lib/providers/status_provider.dart index 38193b8..53fd2f5 100644 --- a/lib/providers/status_provider.dart +++ b/lib/providers/status_provider.dart @@ -2,6 +2,11 @@ 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'; @@ -240,11 +245,31 @@ class StatusProvider with ChangeNotifier { final result = await _serversProvider!.apiClient2!.getServerStatus(); if (result.successful == true) { + final status = result.content as ServerStatus; setServerStatusData( - data: result.content as ServerStatus + 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) { + showDialog( + context: globalNavigatorKey.currentContext!, + builder: (ctx) => UnsupportedVersionModal( + serverVersion: status.serverVersion, + onClose: () { + _serversProvider!.setSelectedServer(null); + } + ) + ); + } + return true; } else { diff --git a/lib/widgets/layout.dart b/lib/widgets/layout.dart index fe3aa1c..8a0a14b 100644 --- a/lib/widgets/layout.dart +++ b/lib/widgets/layout.dart @@ -15,8 +15,8 @@ import 'package:adguard_home_manager/providers/servers_provider.dart'; class Layout extends StatefulWidget { const Layout({ - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => _LayoutState(); From 48f33eee9bcb2f3ba6e07658cf582e23413c4cf4 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 20 Nov 2023 15:00:07 +0100 Subject: [PATCH 088/177] Replaced old http service with new one --- lib/providers/dns_provider.dart | 6 +- lib/providers/logs_provider.dart | 22 +- lib/providers/servers_provider.dart | 40 +- lib/providers/status_provider.dart | 4 +- .../clients/client/logs_list_client.dart | 2 +- lib/screens/settings/dhcp/dhcp.dart | 4 +- .../settings/server_info/server_info.dart | 58 +- lib/screens/settings/settings.dart | 2 +- lib/widgets/bottom_nav_bar.dart | 8 +- lib/widgets/layout.dart | 33 +- lib/widgets/navigation_rail.dart | 6 +- .../servers_list/server_tile_functions.dart | 140 +++++ .../servers_list/servers_list_item.dart | 2 - .../servers_list/servers_tile_item.dart | 570 ++++++++---------- 14 files changed, 466 insertions(+), 431 deletions(-) create mode 100644 lib/widgets/servers_list/server_tile_functions.dart diff --git a/lib/providers/dns_provider.dart b/lib/providers/dns_provider.dart index db1767f..0929f3a 100644 --- a/lib/providers/dns_provider.dart +++ b/lib/providers/dns_provider.dart @@ -42,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; diff --git a/lib/providers/logs_provider.dart b/lib/providers/logs_provider.dart index a3211b5..7861e6d 100644 --- a/lib/providers/logs_provider.dart +++ b/lib/providers/logs_provider.dart @@ -153,7 +153,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 +166,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 +179,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 +204,7 @@ class LogsProvider with ChangeNotifier { resetFilters(); - final result = await _serversProvider!.apiClient!.getLogs( + final result = await _serversProvider!.apiClient2!.getLogs( count: logsQuantity ); @@ -214,8 +214,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 +233,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 +246,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) diff --git a/lib/providers/servers_provider.dart b/lib/providers/servers_provider.dart index addf617..a8f1003 100644 --- a/lib/providers/servers_provider.dart +++ b/lib/providers/servers_provider.dart @@ -7,7 +7,6 @@ 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'; @@ -17,7 +16,7 @@ class ServersProvider with ChangeNotifier { List _serversList = []; Server? _selectedServer; - ApiClient? _apiClient; + // ApiClient? _apiClient; ApiClientV2? _apiClient2; bool _updatingServer = false; @@ -27,9 +26,9 @@ class ServersProvider with ChangeNotifier { data: null, ); - ApiClient? get apiClient { - return _apiClient; - } + // ApiClient? get apiClient { + // return _apiClient; + // } ApiClientV2? get apiClient2 { return _apiClient2; @@ -77,10 +76,10 @@ class ServersProvider with ChangeNotifier { notifyListeners(); } - void setApiClient(ApiClient client) { - _apiClient = client; - notifyListeners(); - } + // void setApiClient(ApiClient client) { + // _apiClient = client; + // notifyListeners(); + // } void setApiClient2(ApiClientV2 client) { _apiClient2 = client; @@ -153,7 +152,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(); @@ -168,7 +168,7 @@ class ServersProvider with ChangeNotifier { final result = await removeServerQuery(_dbInstance!, server.id); if (result == true) { _selectedServer = null; - _apiClient = null; + // _apiClient = null; List newServers = _serversList.where((s) => s.id != server.id).toList(); _serversList = newServers; notifyListeners(); @@ -181,13 +181,13 @@ class ServersProvider with ChangeNotifier { void checkServerUpdatesAvailable({ required Server server, - ApiClient? apiClient + ApiClientV2? apiClient }) async { - final client = apiClient ?? _apiClient; + final client = apiClient ?? _apiClient2; setUpdateAvailableLoadStatus(LoadStatus.loading, true); final result = await client!.checkServerUpdates(); - if (result['result'] == 'success') { - UpdateAvailableData data = UpdateAvailableData.fromJson(result['data']); + if (result.successful == true) { + UpdateAvailableData data = UpdateAvailableData.fromJson(result.content); final gitHubResult = await ExternalRequests.getUpdateChangelog(releaseTag: data.newVersion ?? data.currentVersion); if (gitHubResult.successful == true) { data.changelog = gitHubResult.content; @@ -200,12 +200,12 @@ class ServersProvider with ChangeNotifier { } } - Future initializateServer(Server server, ApiClient apiClient, ApiClientV2 apiClient2) async { + 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: apiClient + apiClient: apiClient2 ); } } @@ -237,11 +237,11 @@ class ServersProvider with ChangeNotifier { if (defaultServer != null) { _selectedServer = defaultServer; - final client = ApiClient(server: defaultServer); + // final client = ApiClient(server: defaultServer); final client2 = ApiClientV2(server: defaultServer); - _apiClient = client; + // _apiClient = client; _apiClient2 = client2; - initializateServer(defaultServer, client, client2); + initializateServer(defaultServer, /*client,*/ client2); } } else { diff --git a/lib/providers/status_provider.dart b/lib/providers/status_provider.dart index 53fd2f5..5fba2dd 100644 --- a/lib/providers/status_provider.dart +++ b/lib/providers/status_provider.dart @@ -149,13 +149,13 @@ class StatusProvider with ChangeNotifier { _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 true; diff --git a/lib/screens/clients/client/logs_list_client.dart b/lib/screens/clients/client/logs_list_client.dart index 8d975a9..422c80c 100644 --- a/lib/screens/clients/client/logs_list_client.dart +++ b/lib/screens/clients/client/logs_list_client.dart @@ -67,7 +67,7 @@ class _LogsListClientState extends State { if (cancelableRequest != null) cancelableRequest!.cancel(); cancelableRequest = CancelableOperation.fromFuture( - serversProvider.apiClient!.getLogs( + serversProvider.apiClient2!.getLogs( count: logsQuantity, offset: offst, search: '"${widget.ip}"' diff --git a/lib/screens/settings/dhcp/dhcp.dart b/lib/screens/settings/dhcp/dhcp.dart index 6440e3b..dbb4a9a 100644 --- a/lib/screens/settings/dhcp/dhcp.dart +++ b/lib/screens/settings/dhcp/dhcp.dart @@ -260,11 +260,11 @@ class _DhcpScreenState extends State { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.restoringLeases); - final result = await serversProvider.apiClient!.restoreAllLeases(); + final result = await serversProvider.apiClient2!.restoreAllLeases(); processModal.close(); - if (result['result'] == 'success') { + if (result.successful == true) { DhcpModel data = dhcpProvider.dhcp!; data.dhcpStatus.staticLeases = []; data.dhcpStatus.leases = []; diff --git a/lib/screens/settings/server_info/server_info.dart b/lib/screens/settings/server_info/server_info.dart index 2a51a16..3032dcf 100644 --- a/lib/screens/settings/server_info/server_info.dart +++ b/lib/screens/settings/server_info/server_info.dart @@ -1,61 +1,37 @@ -import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/functions/desktop_mode.dart'; -import 'package:animations/animations.dart'; 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/settings/server_info/dns_addresses_modal.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/models/server_info.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; -class ServerInformation extends StatelessWidget { - const ServerInformation({Key? key}) : super(key: key); +class ServerInformation extends StatefulWidget { + const ServerInformation({super.key}); @override - Widget build(BuildContext context) { - final serversProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - - return ServerInformationWidget( - serversProvider: serversProvider, - appConfigProvider: appConfigProvider, - ); - } + State createState() => _ServerInformationState(); } -class ServerInformationWidget extends StatefulWidget { - final ServersProvider serversProvider; - final AppConfigProvider appConfigProvider; - - const ServerInformationWidget({ - Key? key, - required this.serversProvider, - required this.appConfigProvider, - }) : super(key: key); - - @override - State createState() => _ServerInformationWidgetState(); -} - -class _ServerInformationWidgetState extends State { +class _ServerInformationState extends State { ServerInfo serverInfo = ServerInfo(loadStatus: LoadStatus.loading); void fetchServerInfo() async { - final result = await Provider.of(context, listen: false).apiClient!.getServerInfo(); - if (mounted) { - if (result['result'] == 'success') { - setState(() { - serverInfo.data = result['data']; - serverInfo.loadStatus = LoadStatus.loaded; - }); - } - else { - setState(() => serverInfo.loadStatus = LoadStatus.error); - } + final result = await Provider.of(context, listen: false).apiClient2!.getServerInfo(); + if (!mounted) return; + if (result.successful == true) { + setState(() { + serverInfo.data = result.content as ServerInfoData; + serverInfo.loadStatus = LoadStatus.loaded; + }); + } + else { + setState(() => serverInfo.loadStatus = LoadStatus.error); } } diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index c3b1337..8d5f384 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -166,7 +166,7 @@ class _SettingsWidgetState extends State { if ( serversProvider.selectedServer != null && statusProvider.serverStatus != null && - serversProvider.apiClient != null + serversProvider.apiClient2 != null ) ...[ SectionLabel(label: AppLocalizations.of(context)!.serverSettings), settingsTile( diff --git a/lib/widgets/bottom_nav_bar.dart b/lib/widgets/bottom_nav_bar.dart index b5ff9df..8d06a8d 100644 --- a/lib/widgets/bottom_nav_bar.dart +++ b/lib/widgets/bottom_nav_bar.dart @@ -8,14 +8,14 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/models/app_screen.dart'; class BottomNavBar extends StatelessWidget { - const BottomNavBar({Key? key}) : super(key: key); + const BottomNavBar({super.key}); @override Widget build(BuildContext context) { final serversProvider = Provider.of(context); final appConfigProvider = Provider.of(context); - List screens = serversProvider.selectedServer != null && serversProvider.apiClient != null + List screens = serversProvider.selectedServer != null && serversProvider.apiClient2 != null ? screensServerConnected : screensSelectServer; @@ -44,12 +44,12 @@ class BottomNavBar extends StatelessWidget { } } - if ((serversProvider.selectedServer == null || serversProvider.apiClient == null) && appConfigProvider.selectedScreen > 1) { + if ((serversProvider.selectedServer == null || serversProvider.apiClient2 == null) && appConfigProvider.selectedScreen > 1) { appConfigProvider.setSelectedScreen(0); } return NavigationBar( - selectedIndex: (serversProvider.selectedServer == null || serversProvider.apiClient == null) && appConfigProvider.selectedScreen > 1 + selectedIndex: (serversProvider.selectedServer == null || serversProvider.apiClient2 == null) && appConfigProvider.selectedScreen > 1 ? 0 : appConfigProvider.selectedScreen, destinations: screens.map((screen) => NavigationDestination( diff --git a/lib/widgets/layout.dart b/lib/widgets/layout.dart index 8a0a14b..4d6e131 100644 --- a/lib/widgets/layout.dart +++ b/lib/widgets/layout.dart @@ -62,7 +62,7 @@ class _LayoutState extends State with WidgetsBindingObserver { final serversProvider = Provider.of(context); final appConfigProvider = Provider.of(context); - final screens = serversProvider.selectedServer != null + final screens = serversProvider.selectedServer != null && serversProvider.apiClient2 != null ? screensServerConnected : screensSelectServer; @@ -120,26 +120,15 @@ class _LayoutState extends State with WidgetsBindingObserver { ), ], ), - if (serversProvider.selectedServer != null) - ...screensServerConnected.asMap().entries.map( - (s) => DrawerTile( - icon: s.value.icon, - title: translatedName(s.value.name), - isSelected: appConfigProvider.selectedScreen == s.key, - onSelect: () => _goBranch(s.key), - withoutTitle: !_drawerExpanded, - ), - ), - if (serversProvider.selectedServer == null) - ...screensSelectServer.asMap().entries.map( - (s) => DrawerTile( - icon: s.value.icon, - title: translatedName(s.value.name), - isSelected: appConfigProvider.selectedScreen == s.key, - onSelect: () => _goBranch(s.key), - withoutTitle: !_drawerExpanded, - ), + ...screens.asMap().entries.map( + (s) => DrawerTile( + icon: s.value.icon, + title: translatedName(s.value.name), + isSelected: appConfigProvider.selectedScreen == s.key, + onSelect: () => _goBranch(s.key), + withoutTitle: !_drawerExpanded, ), + ), ], ), ), @@ -164,7 +153,7 @@ class _LayoutState extends State with WidgetsBindingObserver { ); } else { - final screens = serversProvider.selectedServer != null && serversProvider.apiClient != null + final screens = serversProvider.selectedServer != null && serversProvider.apiClient2 != null ? screensServerConnected : screensSelectServer; @@ -184,7 +173,7 @@ class _LayoutState extends State with WidgetsBindingObserver { : screens[0].child, ), bottomNavigationBar: NavigationBar( - selectedIndex: (serversProvider.selectedServer == null || serversProvider.apiClient == null) && appConfigProvider.selectedScreen > 1 + selectedIndex: (serversProvider.selectedServer == null || serversProvider.apiClient2 == null) && appConfigProvider.selectedScreen > 1 ? 0 : appConfigProvider.selectedScreen, onDestinationSelected: (s) => _goBranch(s), diff --git a/lib/widgets/navigation_rail.dart b/lib/widgets/navigation_rail.dart index 063993c..2d2e1ef 100644 --- a/lib/widgets/navigation_rail.dart +++ b/lib/widgets/navigation_rail.dart @@ -9,7 +9,7 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; class SideNavigationRail extends StatelessWidget { - const SideNavigationRail({Key? key}) : super(key: key); + const SideNavigationRail({super.key}); @override Widget build(BuildContext context) { @@ -46,12 +46,12 @@ class SideNavigationRail extends StatelessWidget { } } - if ((serversProvider.selectedServer == null || serversProvider.apiClient == null) && appConfigProvider.selectedScreen > 1) { + if ((serversProvider.selectedServer == null || serversProvider.apiClient2 == null) && appConfigProvider.selectedScreen > 1) { appConfigProvider.setSelectedScreen(0); } return NavigationRail( - selectedIndex: (serversProvider.selectedServer == null || serversProvider.apiClient == null) && appConfigProvider.selectedScreen > 1 + selectedIndex: (serversProvider.selectedServer == null || serversProvider.apiClient2 == null) && appConfigProvider.selectedScreen > 1 ? 0 : appConfigProvider.selectedScreen, destinations: screens.map((screen) => NavigationRailDestination( diff --git a/lib/widgets/servers_list/server_tile_functions.dart b/lib/widgets/servers_list/server_tile_functions.dart new file mode 100644 index 0000000..e3fa4d6 --- /dev/null +++ b/lib/widgets/servers_list/server_tile_functions.dart @@ -0,0 +1,140 @@ +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/servers_list/delete_modal.dart'; +import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; + +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/models/server_status.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; +import 'package:adguard_home_manager/services/api_client.dart'; +import 'package:adguard_home_manager/services/auth.dart'; +import 'package:adguard_home_manager/classes/process_modal.dart'; +import 'package:adguard_home_manager/models/server.dart'; + +EdgeInsets generateMargins({ + required int index, + required int serversListLength +}) { + if (index == 0) { + return const EdgeInsets.only(top: 16, left: 16, right: 8, bottom: 8); + } + if (index == 1) { + return const EdgeInsets.only(top: 16, left: 8, right: 16, bottom: 8); + } + else if (index == serversListLength-1 && (index+1)%2 == 0) { + return const EdgeInsets.only(top: 8, left: 8, right: 16, bottom: 16); + } + else if (index == serversListLength-1 && (index+1)%2 == 1) { + return const EdgeInsets.only(top: 8, left: 16, right: 8, bottom: 16); + } + else { + if ((index+1)%2 == 0) { + return const EdgeInsets.only(top: 8, left: 8, right: 16, bottom: 8); + } + else { + return const EdgeInsets.only(top: 8, left: 16, right: 8, bottom: 8); + } + } +} + +void showDeleteModal({ + required BuildContext context, + required Server server +}) async { + await Future.delayed(const Duration(seconds: 0), () => { + showDialog( + context: context, + builder: (context) => DeleteModal( + serverToDelete: server, + ), + barrierDismissible: false + ) + }); +} + +void openServerModal({ + required BuildContext context, + required double width, + Server? server, +}) async { + await Future.delayed(const Duration(seconds: 0), (() => { + openServerFormModal(context: context, width: width, server: server) + })); +} + +void connectToServer({ + required BuildContext context, + required Server server +}) async { + final ProcessModal process = ProcessModal(context: context); + process.open(AppLocalizations.of(context)!.connecting); + + final result = server.runningOnHa == true + ? await ServerAuth.loginHA(server) + : await ServerAuth.login(server); + + if (result == AuthStatus.success && context.mounted) { + final serversProvider = Provider.of(context, listen: false); + final statusProvider = Provider.of(context, listen: false); + + final ApiClientV2 apiClient2 = ApiClientV2(server: server); + serversProvider.setApiClient2(apiClient2); + serversProvider.setSelectedServer(server); + + statusProvider.setServerStatusLoad(LoadStatus.loading); + final serverStatus = await apiClient2.getServerStatus(); + if (serverStatus.successful == true) { + statusProvider.setServerStatusData( + data: serverStatus.content as ServerStatus + ); + serversProvider.checkServerUpdatesAvailable( + server: server, + ); + statusProvider.setServerStatusLoad(LoadStatus.loaded); + } + else { + statusProvider.setServerStatusLoad(LoadStatus.error); + } + + process.close(); + } + else { + process.close(); + if (!context.mounted) return; + final appConfigProvider = Provider.of(context, listen: false); + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.cannotConnect, + color: Colors.red + ); + } +} + +void setDefaultServer({ + required BuildContext context, + required Server server +}) async { + final serversProvider = Provider.of(context, listen: false); + final result = await serversProvider.setDefaultServer(server); + if (!context.mounted) return; + final appConfigProvider = Provider.of(context, listen: false); + if (result == null) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.connectionDefaultSuccessfully, + color: Colors.green + ); + } + else { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.connectionDefaultFailed, + color: Colors.red + ); + } +} \ No newline at end of file diff --git a/lib/widgets/servers_list/servers_list_item.dart b/lib/widgets/servers_list/servers_list_item.dart index 492a5e8..e67b436 100644 --- a/lib/widgets/servers_list/servers_list_item.dart +++ b/lib/widgets/servers_list/servers_list_item.dart @@ -104,8 +104,6 @@ class _ServersListItemState extends State with SingleTickerProv : await login(server); if (result['result'] == 'success') { - final ApiClient apiClient = ApiClient(server: server); - serversProvider.setApiClient(apiClient); final ApiClientV2 apiClient2 = ApiClientV2(server: server); serversProvider.setApiClient2(apiClient2); serversProvider.setSelectedServer(server); diff --git a/lib/widgets/servers_list/servers_tile_item.dart b/lib/widgets/servers_list/servers_tile_item.dart index 0422368..61c2a64 100644 --- a/lib/widgets/servers_list/servers_tile_item.dart +++ b/lib/widgets/servers_list/servers_tile_item.dart @@ -4,18 +4,10 @@ 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/add_server/add_server_functions.dart'; -import 'package:adguard_home_manager/widgets/servers_list/delete_modal.dart'; +import 'package:adguard_home_manager/widgets/servers_list/server_tile_functions.dart'; -import 'package:adguard_home_manager/models/server_status.dart'; -import 'package:adguard_home_manager/services/api_client.dart'; -import 'package:adguard_home_manager/classes/process_modal.dart'; -import 'package:adguard_home_manager/functions/snackbar.dart'; -import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; -import 'package:adguard_home_manager/models/app_log.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; -import 'package:adguard_home_manager/services/http_requests.dart'; import 'package:adguard_home_manager/models/server.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; @@ -26,12 +18,12 @@ class ServersTileItem extends StatefulWidget { final double breakingWidth; const ServersTileItem({ - Key? key, + super.key, required this.server, required this.index, required this.onChange, required this.breakingWidth - }) : super(key: key); + }); @override State createState() => _ServersTileItemState(); @@ -46,332 +38,272 @@ class _ServersTileItemState extends State with SingleTickerProv final width = MediaQuery.of(context).size.width; - void showDeleteModal(Server server) async { - await Future.delayed(const Duration(seconds: 0), () => { - showDialog( - context: context, - builder: (context) => DeleteModal( - serverToDelete: server, - ), - barrierDismissible: false - ) - }); - } - - void openServerModal({Server? server}) async { - await Future.delayed(const Duration(seconds: 0), (() => { - openServerFormModal(context: context, width: width, server: server) - })); - } - - void connectToServer(Server server) async { - final ProcessModal process = ProcessModal(context: context); - process.open(AppLocalizations.of(context)!.connecting); - - final result = server.runningOnHa == true - ? await loginHA(server) - : await login(server); - - if (result['result'] == 'success') { - final ApiClient apiClient = ApiClient(server: server); - serversProvider.setApiClient(apiClient); - final ApiClientV2 apiClient2 = ApiClientV2(server: server); - serversProvider.setApiClient2(apiClient2); - serversProvider.setSelectedServer(server); - - statusProvider.setServerStatusLoad(LoadStatus.loading); - final serverStatus = await apiClient2.getServerStatus(); - if (serverStatus.successful == true) { - statusProvider.setServerStatusData( - data: serverStatus.content as ServerStatus - ); - serversProvider.checkServerUpdatesAvailable( - server: server, - ); - statusProvider.setServerStatusLoad(LoadStatus.loaded); - } - else { - statusProvider.setServerStatusLoad(LoadStatus.error); - } - - process.close(); - } - else { - process.close(); - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.cannotConnect, - color: Colors.red - ); - } - } - - void setDefaultServer(Server server) async { - final result = await serversProvider.setDefaultServer(server); - if (result == null) { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.connectionDefaultSuccessfully, - color: Colors.green - ); - } - else { - appConfigProvider.addLog( - AppLog( - type: 'set_default_server', - dateTime: DateTime.now(), - message: result.toString() - ) - ); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.connectionDefaultFailed, - color: Colors.red - ); - } - } - - Widget leadingIcon(Server server) { - if (server.defaultServer == true) { - return Stack( - alignment: Alignment.center, - children: [ - Icon( - Icons.storage_rounded, - color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id - ? statusProvider.serverStatus != null - ? Colors.green - : Colors.orange - : null, - ), - SizedBox( - width: 25, - height: 25, - child: Stack( - alignment: Alignment.bottomRight, - children: [ - Container( - padding: const EdgeInsets.all(1), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(20) - ), - child: Icon( - Icons.star, - color: Theme.of(context).colorScheme.onPrimaryContainer, - size: 10, - ), - ), - ], - ), - ) - ], - ); - } - else { - return Icon( - Icons.storage_rounded, - color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id - ? statusProvider.serverStatus != null - ? Colors.green - : Colors.orange - : null, - ); - } - } - - Widget topRow(Server server, int index) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Row( - children: [ - Container( - margin: const EdgeInsets.only(right: 16), - child: leadingIcon(server), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "${server.connectionMethod}://${server.domain}${server.path ?? ""}${server.port != null ? ':${server.port}' : ""}", - textAlign: TextAlign.left, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Column( - children: [ - const SizedBox(height: 3), - Text( - server.name, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ) - ], - ) - ], - ), - ), - ], - ), - ), - ], - ); - } - - Widget bottomRow(Server server, int index) { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - PopupMenuButton( - itemBuilder: (context) => [ - PopupMenuItem( - enabled: server.defaultServer == false - ? true - : false, - onTap: server.defaultServer == false - ? (() => setDefaultServer(server)) - : null, - child: SizedBox( - child: Row( - children: [ - const Icon(Icons.star), - const SizedBox(width: 15), - Text( - server.defaultServer == true - ? AppLocalizations.of(context)!.defaultConnection - : AppLocalizations.of(context)!.setDefault, - ) - ], - ), - ) - ), - PopupMenuItem( - onTap: (() => openServerModal(server: server)), - child: Row( - children: [ - const Icon(Icons.edit), - const SizedBox(width: 15), - Text(AppLocalizations.of(context)!.edit) - ], - ) - ), - PopupMenuItem( - onTap: (() => showDeleteModal(server)), - child: Row( - children: [ - const Icon(Icons.delete), - const SizedBox(width: 15), - Text(AppLocalizations.of(context)!.delete) - ], - ) - ), - ] - ), - SizedBox( - child: serversProvider.selectedServer != null && - serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && statusProvider.serverStatus != null && - serversProvider.selectedServer?.id == server.id - ? Padding( - padding: const EdgeInsets.only(right: 16), - child: Row( - children: [ - Icon( - serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && statusProvider.serverStatus != null - ? Icons.check - : Icons.warning, - color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && statusProvider.serverStatus != null - ? Colors.green - : Colors.orange, - ), - const SizedBox(width: 10), - Text( - serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && statusProvider.serverStatus != null - ? AppLocalizations.of(context)!.connected - : AppLocalizations.of(context)!.selectedDisconnected, - style: TextStyle( - color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && statusProvider.serverStatus != null - ? Colors.green - : Colors.orange, - fontWeight: FontWeight.w500 - ), - ) - ], - ), - ) - : Container( - margin: const EdgeInsets.only(right: 10), - child: FilledButton.icon( - icon: const Icon(Icons.login), - onPressed: () => connectToServer(server), - label: Text(AppLocalizations.of(context)!.connect), - ), - ), - ) - ], - ) - ], - ); - } - - EdgeInsets generateMargins(int index) { - if (index == 0) { - return const EdgeInsets.only(top: 16, left: 16, right: 8, bottom: 8); - } - if (index == 1) { - return const EdgeInsets.only(top: 16, left: 8, right: 16, bottom: 8); - } - else if (index == serversProvider.serversList.length-1 && (index+1)%2 == 0) { - return const EdgeInsets.only(top: 8, left: 8, right: 16, bottom: 16); - } - else if (index == serversProvider.serversList.length-1 && (index+1)%2 == 1) { - return const EdgeInsets.only(top: 8, left: 16, right: 8, bottom: 16); - } - else { - if ((index+1)%2 == 0) { - return const EdgeInsets.only(top: 8, left: 8, right: 16, bottom: 8); - } - else { - return const EdgeInsets.only(top: 8, left: 16, right: 8, bottom: 8); - } - } - } - return FractionallySizedBox( widthFactor: width > widget.breakingWidth ? 0.5 : 1, child: Card( margin: width > widget.breakingWidth - ? generateMargins(widget.index) + ? generateMargins(index: widget.index, serversListLength: serversProvider.serversList.length) : const EdgeInsets.symmetric(vertical: 8, horizontal: 16), child: Column( children: [ Padding( padding: const EdgeInsets.all(16), - child: topRow(widget.server, widget.index), + child: _TopRow(server: widget.server, index: widget.index) ), Padding( padding: const EdgeInsets.only( left: 8, right: 8, bottom: 16 ), - child: bottomRow(widget.server, widget.index), + child: _BottomRow( + server: widget.server, + connectToServer: (s) => connectToServer(context: context, server: s), + openServerModal: (s) => openServerModal(context: context, server: s, width: width), + setDefaultServer: (s) => setDefaultServer(context: context, server: s), + showDeleteModal: (s) => showDeleteModal(context: context, server: s), + ) ) ], ), ), ); } +} + +class _LeadingIcon extends StatelessWidget { + final Server server; + + const _LeadingIcon({ + required this.server, + }); + + @override + Widget build(BuildContext context) { + final serversProvider = Provider.of(context); + final statusProvider = Provider.of(context); + + if (server.defaultServer == true) { + return Stack( + alignment: Alignment.center, + children: [ + Icon( + Icons.storage_rounded, + color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id + ? statusProvider.serverStatus != null + ? Colors.green + : Colors.orange + : null, + ), + SizedBox( + width: 25, + height: 25, + child: Stack( + alignment: Alignment.bottomRight, + children: [ + Container( + padding: const EdgeInsets.all(1), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(20) + ), + child: Icon( + Icons.star, + color: Theme.of(context).colorScheme.onPrimaryContainer, + size: 10, + ), + ), + ], + ), + ) + ], + ); + } + else { + return Icon( + Icons.storage_rounded, + color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id + ? statusProvider.serverStatus != null + ? Colors.green + : Colors.orange + : null, + ); + } + } +} + +class _TopRow extends StatelessWidget { + final Server server; + final int index; + + const _TopRow({ + required this.server, + required this.index, + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + Container( + margin: const EdgeInsets.only(right: 16), + child: _LeadingIcon(server: server), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${server.connectionMethod}://${server.domain}${server.path ?? ""}${server.port != null ? ':${server.port}' : ""}", + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurface + ), + ), + Column( + children: [ + const SizedBox(height: 3), + Text( + server.name, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ) + ], + ) + ], + ), + ), + ], + ), + ), + ], + ); + } +} + +class _BottomRow extends StatelessWidget { + final Server server; + final void Function(Server) setDefaultServer; + final void Function(Server) openServerModal; + final void Function(Server) showDeleteModal; + final void Function(Server) connectToServer; + + const _BottomRow({ + required this.server, + required this.setDefaultServer, + required this.openServerModal, + required this.showDeleteModal, + required this.connectToServer, + }); + + @override + Widget build(BuildContext context) { + final serversProvider = Provider.of(context); + final statusProvider = Provider.of(context); + + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + PopupMenuButton( + itemBuilder: (context) => [ + PopupMenuItem( + enabled: server.defaultServer == false + ? true + : false, + onTap: server.defaultServer == false + ? (() => setDefaultServer(server)) + : null, + child: SizedBox( + child: Row( + children: [ + const Icon(Icons.star), + const SizedBox(width: 15), + Text( + server.defaultServer == true + ? AppLocalizations.of(context)!.defaultConnection + : AppLocalizations.of(context)!.setDefault, + ) + ], + ), + ) + ), + PopupMenuItem( + onTap: (() => openServerModal(server)), + child: Row( + children: [ + const Icon(Icons.edit), + const SizedBox(width: 15), + Text(AppLocalizations.of(context)!.edit) + ], + ) + ), + PopupMenuItem( + onTap: (() => showDeleteModal(server)), + child: Row( + children: [ + const Icon(Icons.delete), + const SizedBox(width: 15), + Text(AppLocalizations.of(context)!.delete) + ], + ) + ), + ] + ), + SizedBox( + child: serversProvider.selectedServer != null && + serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && statusProvider.serverStatus != null && + serversProvider.selectedServer?.id == server.id + ? Padding( + padding: const EdgeInsets.only(right: 16), + child: Row( + children: [ + Icon( + serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && statusProvider.serverStatus != null + ? Icons.check + : Icons.warning, + color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && statusProvider.serverStatus != null + ? Colors.green + : Colors.orange, + ), + const SizedBox(width: 10), + Text( + serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && statusProvider.serverStatus != null + ? AppLocalizations.of(context)!.connected + : AppLocalizations.of(context)!.selectedDisconnected, + style: TextStyle( + color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && statusProvider.serverStatus != null + ? Colors.green + : Colors.orange, + fontWeight: FontWeight.w500 + ), + ) + ], + ), + ) + : Container( + margin: const EdgeInsets.only(right: 10), + child: FilledButton.icon( + icon: const Icon(Icons.login), + onPressed: () => connectToServer(server), + label: Text(AppLocalizations.of(context)!.connect), + ), + ), + ) + ], + ) + ], + ); + } } \ No newline at end of file From c2af933eb507aedc4b1cb1d50e306c958e2ca990 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 20 Nov 2023 15:00:35 +0100 Subject: [PATCH 089/177] Removed unused import --- lib/widgets/servers_list/servers_tile_item.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/widgets/servers_list/servers_tile_item.dart b/lib/widgets/servers_list/servers_tile_item.dart index 61c2a64..bf28ade 100644 --- a/lib/widgets/servers_list/servers_tile_item.dart +++ b/lib/widgets/servers_list/servers_tile_item.dart @@ -7,7 +7,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/servers_list/server_tile_functions.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/models/server.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; @@ -33,8 +32,6 @@ class _ServersTileItemState extends State with SingleTickerProv @override Widget build(BuildContext context) { final serversProvider = Provider.of(context); - final statusProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); final width = MediaQuery.of(context).size.width; From 34bff2f506b99c9fb4d420a8940d4a266ceda3a7 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 20 Nov 2023 15:16:20 +0100 Subject: [PATCH 090/177] Improved server version check --- lib/classes/process_modal.dart | 14 +++----- lib/functions/clear_dns_cache.dart | 2 +- lib/providers/status_provider.dart | 6 ++-- lib/screens/clients/added_list.dart | 4 +-- lib/screens/clients/fab.dart | 2 +- lib/screens/clients/search_clients.dart | 4 +-- lib/screens/filters/add_button.dart | 4 +-- .../filters/details/list_details_screen.dart | 4 +-- lib/screens/filters/filters.dart | 8 ++--- lib/screens/filters/list_options_menu.dart | 2 +- .../modals/blocked_services_screen.dart | 2 +- .../filters/selection/selection_screen.dart | 4 +-- .../logs/details/log_details_screen.dart | 2 +- lib/screens/logs/logs_list_appbar.dart | 4 +-- .../access_settings/clients_list.dart | 4 +-- lib/screens/settings/dhcp/dhcp.dart | 6 ++-- lib/screens/settings/dhcp/dhcp_leases.dart | 4 +-- lib/screens/settings/dns/bootstrap_dns.dart | 2 +- lib/screens/settings/dns/cache_config.dart | 2 +- .../settings/dns/dns_server_settings.dart | 2 +- .../settings/dns/private_reverse_servers.dart | 2 +- lib/screens/settings/dns/upstream_dns.dart | 2 +- .../settings/dns_rewrites/dns_rewrites.dart | 6 ++-- .../settings/encryption/encryption.dart | 2 +- .../settings/safe_search_settings.dart | 2 +- .../settings/update_server/update.dart | 2 +- lib/widgets/domain_options.dart | 2 +- .../servers_list/server_tile_functions.dart | 32 +++++++++++++++++-- .../servers_list/servers_list_item.dart | 2 +- 29 files changed, 77 insertions(+), 57 deletions(-) diff --git a/lib/classes/process_modal.dart b/lib/classes/process_modal.dart index 6febebb..ac3db92 100644 --- a/lib/classes/process_modal.dart +++ b/lib/classes/process_modal.dart @@ -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!); } } \ No newline at end of file diff --git a/lib/functions/clear_dns_cache.dart b/lib/functions/clear_dns_cache.dart index d0b44cd..4a6fe53 100644 --- a/lib/functions/clear_dns_cache.dart +++ b/lib/functions/clear_dns_cache.dart @@ -12,7 +12,7 @@ import 'package:adguard_home_manager/models/server.dart'; Future clearDnsCache(BuildContext context, Server server) async { final serversProvider = Provider.of(context, listen: false); - final ProcessModal processModal = ProcessModal(context: context); + final ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.clearingDnsCache); final result = await serversProvider.apiClient2!.resetDnsCache(); diff --git a/lib/providers/status_provider.dart b/lib/providers/status_provider.dart index 5fba2dd..0c519f8 100644 --- a/lib/providers/status_provider.dart +++ b/lib/providers/status_provider.dart @@ -237,7 +237,8 @@ class StatusProvider with ChangeNotifier { } Future getServerStatus({ - bool? withLoadingIndicator + bool? withLoadingIndicator, + bool? overrideCheckServerVersion }) async { if (withLoadingIndicator == true) { _loadStatus = LoadStatus.loading; @@ -258,7 +259,7 @@ class StatusProvider with ChangeNotifier { referenceVersion: MinimumServerVersion.stable, referenceVersionBeta: MinimumServerVersion.beta ); - if (validVersion == false) { + if (validVersion == false && overrideCheckServerVersion != true) { showDialog( context: globalNavigatorKey.currentContext!, builder: (ctx) => UnsupportedVersionModal( @@ -269,7 +270,6 @@ class StatusProvider with ChangeNotifier { ) ); } - return true; } else { diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index 2c93a04..124f32c 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -75,7 +75,7 @@ class _AddedListState extends State { final width = MediaQuery.of(context).size.width; 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); @@ -99,7 +99,7 @@ class _AddedListState extends State { } 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); diff --git a/lib/screens/clients/fab.dart b/lib/screens/clients/fab.dart index ce291c2..64fd098 100644 --- a/lib/screens/clients/fab.dart +++ b/lib/screens/clients/fab.dart @@ -25,7 +25,7 @@ class ClientsFab extends StatelessWidget { 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); diff --git a/lib/screens/clients/search_clients.dart b/lib/screens/clients/search_clients.dart index bbc330c..895f458 100644 --- a/lib/screens/clients/search_clients.dart +++ b/lib/screens/clients/search_clients.dart @@ -86,7 +86,7 @@ class _SearchClientsState extends State { 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); @@ -110,7 +110,7 @@ class _SearchClientsState extends State { } 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); diff --git a/lib/screens/filters/add_button.dart b/lib/screens/filters/add_button.dart index fbab84c..7dd4d8c 100644 --- a/lib/screens/filters/add_button.dart +++ b/lib/screens/filters/add_button.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); @@ -83,7 +83,7 @@ class AddFiltersButton extends StatelessWidget { } 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); diff --git a/lib/screens/filters/details/list_details_screen.dart b/lib/screens/filters/details/list_details_screen.dart index 8c8199a..b5e305c 100644 --- a/lib/screens/filters/details/list_details_screen.dart +++ b/lib/screens/filters/details/list_details_screen.dart @@ -81,7 +81,7 @@ class _ListDetailsScreenState extends State { required FilteringListActions action, required Filter filterList, }) async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open( action == FilteringListActions.edit ? AppLocalizations.of(context)!.savingList @@ -249,7 +249,7 @@ class _ListDetailsScreenState extends State { 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, diff --git a/lib/screens/filters/filters.dart b/lib/screens/filters/filters.dart index 7953b81..c0a7e20 100644 --- a/lib/screens/filters/filters.dart +++ b/lib/screens/filters/filters.dart @@ -51,7 +51,7 @@ class _FiltersState extends State { 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; @@ -97,7 +97,7 @@ class _FiltersState extends State { } void enableDisableFiltering() async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open( statusProvider.serverStatus!.filteringEnabled == true ? AppLocalizations.of(context)!.disableFiltering @@ -125,7 +125,7 @@ class _FiltersState extends State { } 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); @@ -155,7 +155,7 @@ class _FiltersState extends State { } 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); diff --git a/lib/screens/filters/list_options_menu.dart b/lib/screens/filters/list_options_menu.dart index 853eb7c..2e47d36 100644 --- a/lib/screens/filters/list_options_menu.dart +++ b/lib/screens/filters/list_options_menu.dart @@ -40,7 +40,7 @@ class ListOptionsMenu extends StatelessWidget { 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 diff --git a/lib/screens/filters/modals/blocked_services_screen.dart b/lib/screens/filters/modals/blocked_services_screen.dart index cefdda5..6169d0b 100644 --- a/lib/screens/filters/modals/blocked_services_screen.dart +++ b/lib/screens/filters/modals/blocked_services_screen.dart @@ -60,7 +60,7 @@ class _BlockedServicesScreenStateWidget extends State { } void updateBlockedServices() async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.updating); final result = await filteringProvider.updateBlockedServices(values); diff --git a/lib/screens/filters/selection/selection_screen.dart b/lib/screens/filters/selection/selection_screen.dart index 108544e..cde1f1d 100644 --- a/lib/screens/filters/selection/selection_screen.dart +++ b/lib/screens/filters/selection/selection_screen.dart @@ -76,7 +76,7 @@ class _SelectionScreenState extends State with TickerProviderSt selectedBlacklists: _selectedBlacklists, onDelete: () async { Navigator.pop(context); - final processModal = ProcessModal(context: context); + final processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.processingLists); final result = await filteringProvider.enableDisableMultipleLists( blacklists: _selectedBlacklists, @@ -107,7 +107,7 @@ class _SelectionScreenState extends State with TickerProviderSt selectedBlacklists: _selectedBlacklists, onDelete: () async { Navigator.pop(context); - final processModal = ProcessModal(context: context); + final processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.deletingLists); final result = await filteringProvider.deleteMultipleLists( blacklists: _selectedBlacklists, diff --git a/lib/screens/logs/details/log_details_screen.dart b/lib/screens/logs/details/log_details_screen.dart index 510c2e2..ff88eaa 100644 --- a/lib/screens/logs/details/log_details_screen.dart +++ b/lib/screens/logs/details/log_details_screen.dart @@ -59,7 +59,7 @@ class LogDetailsScreen extends StatelessWidget { } void blockUnblock(String domain, String newStatus) async { - final ProcessModal processModal = ProcessModal(context: context); + final ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.savingUserFilters); final rules = await statusProvider.blockUnblockDomain( diff --git a/lib/screens/logs/logs_list_appbar.dart b/lib/screens/logs/logs_list_appbar.dart index d2f0ec9..f467d26 100644 --- a/lib/screens/logs/logs_list_appbar.dart +++ b/lib/screens/logs/logs_list_appbar.dart @@ -39,7 +39,7 @@ class LogsListAppBar extends StatelessWidget { final width = MediaQuery.of(context).size.width; void updateConfig(Map data) async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.updatingSettings); final result = await serversProvider.apiClient2!.updateQueryLogParameters(data: data); @@ -63,7 +63,7 @@ class LogsListAppBar extends StatelessWidget { } void clearQueries() async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.updatingSettings); final result = await serversProvider.apiClient2!.clearLogs(); diff --git a/lib/screens/settings/access_settings/clients_list.dart b/lib/screens/settings/access_settings/clients_list.dart index 4dedc24..0b83a4d 100644 --- a/lib/screens/settings/access_settings/clients_list.dart +++ b/lib/screens/settings/access_settings/clients_list.dart @@ -96,7 +96,7 @@ class _ClientsListState extends State { body['blocked_hosts'] = body['blocked_hosts']!.where((c) => c != client).toList(); } - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.removingClient); final result = await clientsProvider.removeClientList(client, type); @@ -129,7 +129,7 @@ class _ClientsListState extends State { } void confirmAddItem(String item, AccessSettingsList type) async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.removingClient); final result = await clientsProvider.addClientList(item, type); diff --git a/lib/screens/settings/dhcp/dhcp.dart b/lib/screens/settings/dhcp/dhcp.dart index dbb4a9a..84a4e76 100644 --- a/lib/screens/settings/dhcp/dhcp.dart +++ b/lib/screens/settings/dhcp/dhcp.dart @@ -192,7 +192,7 @@ class _DhcpScreenState extends State { final width = MediaQuery.of(context).size.width; void saveSettings() async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.savingSettings); final result = await serversProvider.apiClient2!.saveDhcpConfig( data: { @@ -232,7 +232,7 @@ class _DhcpScreenState extends State { void restoreConfig() async { Future.delayed(const Duration(seconds: 0), () async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.restoringConfig); final result = await serversProvider.apiClient2!.resetDhcpConfig(); if (!mounted) return; @@ -257,7 +257,7 @@ class _DhcpScreenState extends State { void restoreLeases() async { Future.delayed(const Duration(seconds: 0), () async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.restoringLeases); final result = await serversProvider.apiClient2!.restoreAllLeases(); diff --git a/lib/screens/settings/dhcp/dhcp_leases.dart b/lib/screens/settings/dhcp/dhcp_leases.dart index d6f5051..91bd1cf 100644 --- a/lib/screens/settings/dhcp/dhcp_leases.dart +++ b/lib/screens/settings/dhcp/dhcp_leases.dart @@ -35,7 +35,7 @@ class DhcpLeases extends StatelessWidget { final width = MediaQuery.of(context).size.width; void deleteLease(Lease lease) async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.deleting); final result = await dhcpProvider.deleteLease(lease); @@ -59,7 +59,7 @@ class DhcpLeases extends StatelessWidget { } void createLease(Lease lease) async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.creating); final result = await dhcpProvider.createLease(lease); diff --git a/lib/screens/settings/dns/bootstrap_dns.dart b/lib/screens/settings/dns/bootstrap_dns.dart index a439367..2ae3f4f 100644 --- a/lib/screens/settings/dns/bootstrap_dns.dart +++ b/lib/screens/settings/dns/bootstrap_dns.dart @@ -70,7 +70,7 @@ class _BootstrapDnsScreenState extends State { final width = MediaQuery.of(context).size.width; void saveData() async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.savingConfig); final result = await dnsProvider.saveBootstrapDnsConfig({ diff --git a/lib/screens/settings/dns/cache_config.dart b/lib/screens/settings/dns/cache_config.dart index 13d1836..7f6f63a 100644 --- a/lib/screens/settings/dns/cache_config.dart +++ b/lib/screens/settings/dns/cache_config.dart @@ -73,7 +73,7 @@ class _CacheConfigDnsScreenState extends State { final width = MediaQuery.of(context).size.width; void saveData() async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.savingConfig); final result = await dnsProvider.saveCacheCacheConfig({ diff --git a/lib/screens/settings/dns/dns_server_settings.dart b/lib/screens/settings/dns/dns_server_settings.dart index 6602af0..777821f 100644 --- a/lib/screens/settings/dns/dns_server_settings.dart +++ b/lib/screens/settings/dns/dns_server_settings.dart @@ -103,7 +103,7 @@ class _DnsServerSettingsScreenState extends State { final width = MediaQuery.of(context).size.width; void saveData() async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.savingConfig); final result = await dnsProvider.saveDnsServerConfig({ diff --git a/lib/screens/settings/dns/private_reverse_servers.dart b/lib/screens/settings/dns/private_reverse_servers.dart index 98b1cb9..f91c593 100644 --- a/lib/screens/settings/dns/private_reverse_servers.dart +++ b/lib/screens/settings/dns/private_reverse_servers.dart @@ -94,7 +94,7 @@ class _PrivateReverseDnsServersScreenState extends State { } void saveData() async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.savingConfig); final result = await dnsProvider.saveUpstreamDnsConfig({ diff --git a/lib/screens/settings/dns_rewrites/dns_rewrites.dart b/lib/screens/settings/dns_rewrites/dns_rewrites.dart index cd4c8bd..236b107 100644 --- a/lib/screens/settings/dns_rewrites/dns_rewrites.dart +++ b/lib/screens/settings/dns_rewrites/dns_rewrites.dart @@ -59,7 +59,7 @@ class _DnsRewritesScreenState extends State { final width = MediaQuery.of(context).size.width; void deleteDnsRewrite(RewriteRules rule) async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.deleting); final result = await rewriteRulesProvider.deleteDnsRewrite(rule); @@ -83,7 +83,7 @@ class _DnsRewritesScreenState extends State { } void addDnsRewrite(RewriteRules rule, _) async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.addingRewrite); final result = await rewriteRulesProvider.addDnsRewrite(rule); @@ -107,7 +107,7 @@ class _DnsRewritesScreenState extends State { } void updateRewriteRule(RewriteRules newRule, RewriteRules? previousRule) async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.updatingRule); final result = await rewriteRulesProvider.editDnsRewrite(newRule, previousRule!); diff --git a/lib/screens/settings/encryption/encryption.dart b/lib/screens/settings/encryption/encryption.dart index fc38216..63d9620 100644 --- a/lib/screens/settings/encryption/encryption.dart +++ b/lib/screens/settings/encryption/encryption.dart @@ -201,7 +201,7 @@ class _EncryptionSettingsState extends State { final width = MediaQuery.of(context).size.width; void saveData() async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.savingConfig); final result = await serversProvider.apiClient2!.saveEncryptionSettings( diff --git a/lib/screens/settings/safe_search_settings.dart b/lib/screens/settings/safe_search_settings.dart index 9ef8359..4f6713a 100644 --- a/lib/screens/settings/safe_search_settings.dart +++ b/lib/screens/settings/safe_search_settings.dart @@ -74,7 +74,7 @@ class _SafeSearchSettingsScreenState extends State { final width = MediaQuery.of(context).size.width; void saveConfig() async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.savingSettings); final result = await statusProvider.updateSafeSearchConfig({ diff --git a/lib/screens/settings/update_server/update.dart b/lib/screens/settings/update_server/update.dart index f1c63a8..dd91f6e 100644 --- a/lib/screens/settings/update_server/update.dart +++ b/lib/screens/settings/update_server/update.dart @@ -32,7 +32,7 @@ class UpdateScreen extends StatelessWidget { } void update() async { - ProcessModal processModal = ProcessModal(context: context); + ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.requestingUpdate); final result = await serversProvider.apiClient2!.requestUpdateServer(); diff --git a/lib/widgets/domain_options.dart b/lib/widgets/domain_options.dart index 58409d9..3c4bb79 100644 --- a/lib/widgets/domain_options.dart +++ b/lib/widgets/domain_options.dart @@ -39,7 +39,7 @@ class DomainOptions extends StatelessWidget { final appConfigProvider = Provider.of(context); void blockUnblock(String domain, String newStatus) async { - final ProcessModal processModal = ProcessModal(context: context); + final ProcessModal processModal = ProcessModal(); processModal.open(AppLocalizations.of(context)!.savingUserFilters); final rules = await statusProvider.blockUnblockDomain( diff --git a/lib/widgets/servers_list/server_tile_functions.dart b/lib/widgets/servers_list/server_tile_functions.dart index e3fa4d6..ec3c92e 100644 --- a/lib/widgets/servers_list/server_tile_functions.dart +++ b/lib/widgets/servers_list/server_tile_functions.dart @@ -2,9 +2,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/widgets/add_server/unsupported_version_modal.dart'; import 'package:adguard_home_manager/widgets/servers_list/delete_modal.dart'; import 'package:adguard_home_manager/widgets/add_server/add_server_functions.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/constants/enums.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/models/server_status.dart'; @@ -71,7 +75,7 @@ void connectToServer({ required BuildContext context, required Server server }) async { - final ProcessModal process = ProcessModal(context: context); + final ProcessModal process = ProcessModal(); process.open(AppLocalizations.of(context)!.connecting); final result = server.runningOnHa == true @@ -84,13 +88,35 @@ void connectToServer({ final ApiClientV2 apiClient2 = ApiClientV2(server: server); serversProvider.setApiClient2(apiClient2); - serversProvider.setSelectedServer(server); statusProvider.setServerStatusLoad(LoadStatus.loading); final serverStatus = await apiClient2.getServerStatus(); if (serverStatus.successful == true) { + final status = serverStatus.content as ServerStatus; + + // Check if server version is supported + final validVersion = serverVersionIsAhead( + currentVersion: status.serverVersion, + referenceVersion: MinimumServerVersion.stable, + referenceVersionBeta: MinimumServerVersion.beta + ); + if (validVersion == false) { + process.close(); + showDialog( + context: globalNavigatorKey.currentContext!, + builder: (ctx) => UnsupportedVersionModal( + serverVersion: status.serverVersion, + onClose: () { + serversProvider.setSelectedServer(null); + } + ) + ); + return; + } + + serversProvider.setSelectedServer(server); statusProvider.setServerStatusData( - data: serverStatus.content as ServerStatus + data: status ); serversProvider.checkServerUpdatesAvailable( server: server, diff --git a/lib/widgets/servers_list/servers_list_item.dart b/lib/widgets/servers_list/servers_list_item.dart index e67b436..d3149c1 100644 --- a/lib/widgets/servers_list/servers_list_item.dart +++ b/lib/widgets/servers_list/servers_list_item.dart @@ -96,7 +96,7 @@ class _ServersListItemState extends State with SingleTickerProv } void connectToServer(Server server) async { - final ProcessModal process = ProcessModal(context: context); + final ProcessModal process = ProcessModal(); process.open(AppLocalizations.of(context)!.connecting); final result = server.runningOnHa == true From 802f2ca3289afd1138915ec0847d18cda1e6e547 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 20 Nov 2023 15:22:27 +0100 Subject: [PATCH 091/177] Updated turkish translation --- lib/l10n/app_tr.arb | 67 ++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index 27b066c..1fdb739 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -65,7 +65,7 @@ "theme": "Tema", "light": "Aydınlık", "dark": "Karanlık", - "systemDefined": "Sistemle uyumlu olsun", + "systemDefined": "Sistemle uyumlu hale getir", "close": "Kapat", "connectedTo": "Bağlandı:", "selectedServer": "Seçili sunucu:", @@ -78,7 +78,7 @@ "helperPath": "Ters proxy kullanıyorsanız", "aboutApp": "Uygulama hakkında", "appVersion": "Uygulama sürümü", - "createdBy": "Yapımcı", + "createdBy": "Geliştirici", "clients": "İstemciler", "allowed": "İzin verildi", "blocked": "Engellendi", @@ -102,9 +102,9 @@ "copyLogsClipboard": "Günlükleri panoya kopyala", "logsCopiedClipboard": "Günlükler panoya kopyalandı", "advancedSettings": "Gelişmiş ayarlar", - "dontCheckCertificate": "SSL sertifikası kontrol edilmesin", + "dontCheckCertificate": "SSL sertifikasını asla kontrol etme", "dontCheckCertificateDescription": "Sunucunun SSL sertifikası doğrulamasını geçersiz kılar", - "advancedSetupDescription": "Gelişmiş seçenekler", + "advancedSetupDescription": "Gelişmiş seçenekleri yönet", "settingsUpdatedSuccessfully": "Ayarlar başarıyla güncellendi.", "cannotUpdateSettings": "Ayarlar güncellenemiyor.", "restartAppTakeEffect": "Uygulamayı yeniden başlat", @@ -135,7 +135,7 @@ "blocklist": "Engelleme Listesi", "request": "İstek", "domain": "Alan adı", - "type": "Tür", + "type": "Tip", "clas": "Sınıf", "response": "Yanıt", "dnsServer": "DNS sunucusu", @@ -174,9 +174,9 @@ "malwarePhishingBlocked": "Engellenen zararlı içerikler", "blockedAdultWebsites": "Engellenen yetişkin içerikler", "generalSettings": "Genel ayarlar", - "generalSettingsDescription": "Çeşitli farklı 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ı 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", @@ -233,12 +233,12 @@ "noWhiteLists": "Beyaz listeler yok", "addWhitelist": "Beyaz liste ekle", "addBlacklist": "Kara liste ekle", - "urlNotValid": "URL Adresi geçerli değil", - "urlAbsolutePath": "URL adresi veya kesin dosya yolu", + "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 URL adresi geçersiz", + "listUrlInvalid": "Liste bağlantı adresi geçersiz", "listNotAdded": "Liste eklenemedi", "listDetails": "Liste detayları", "listType": "Liste türü", @@ -260,11 +260,11 @@ "deleteListMessage": "Bu listeyi silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", "serverSettings": "Sunucu ayarları", "serverInformation": "Sunucu bilgisi", - "serverInformationDescription": "Sunucu bilgisi ve durumu", + "serverInformationDescription": "Sunucu bilgisi ve durumunu öğren", "loadingServerInfo": "Sunucu bilgisi yükleniyor...", "serverInfoNotLoaded": "Sunucu bilgisi yüklenemedi.", "dnsAddresses": "DNS adresleri", - "seeDnsAddresses": "DNS adreslerine bak", + "seeDnsAddresses": "DNS adreslerine göz at", "dnsPort": "DNS bağlantı noktası", "httpPort": "HTTP bağlantı noktası", "protectionEnabled": "Koruma etkin mi?", @@ -450,14 +450,14 @@ "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 yukarı akış sunucuları) istemcilerin ana bilgisayar adlarını tersine çöz.", "dnsServerSettings": "AdGuard Home DNS sunucusu ayarları", - "limitRequestsSecond": "Saniye başına hız limiti", + "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", + "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)", + "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", + "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", @@ -471,9 +471,9 @@ "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).", + "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).", + "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...", @@ -515,7 +515,7 @@ "pasteCertificateContent": "Sertifika içeriğini yapıştır", "certificatePath": "Sertifika dosya yolu", "certificateContent": "Sertifika içeriği", - "privateKey": "Özel anahtar", + "privateKey": "Özel anahtarlar", "privateKeyFile": "Özel anahtar dosyası belirle", "pastePrivateKey": "Özel anahtar içeriğini yapıştır", "usePreviousKey": "Önceden kaydedilmiş anahtarı kullan", @@ -583,7 +583,7 @@ "dnsCacheCleared": "DNS önbelleği başarıyla temizlendi", "clearingDnsCache": "Önbellek temizleniyor...", "dnsCacheNotCleared": "DNS önbelleği temizlenemedi", - "clientsSelected": "Seçilen istemciler", + "clientsSelected": "Seçilmiş istemci", "invalidDomain": "Geçersiz alan adı", "loadingBlockedServicesList": "Engellenen hizmetler listesi yükleniyor...", "blockedServicesListNotLoaded": "Engellenen hizmetler listesi yüklenemedi", @@ -618,18 +618,18 @@ "copiedClipboard": "Panoya kopyalandı", "seeDetails": "Detayları gör", "listNotAvailable": "Liste mevcut değil", - "copyListUrl": "Liste URL adresini kopyala", - "listUrlCopied": "Panoya kopyalanan URL adresini listeleyin", + "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üklerde istemci adı yerine her zaman IP adresini göster", + "ipLogsDescription": "Günlükler listesinde istemci adı yerine IP adresini göster", "application": "Uygulama", "combinedChart": "Birleştirilmiş grafik", - "combinedChartDescription": "Tüm grafikleri bir araya getir", + "combinedChartDescription": "Tüm grafikleri bir araya getirir", "statistics": "İstatistikler", "errorLoadFilters": "Filtreler yüklenirken hata oluştu.", "clientRemovedSuccessfully": "İstemci başarıyla kaldırıldı.", @@ -669,5 +669,22 @@ "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" + "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ı 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}" } From ed9293fb70c61119eec1a39ad07aa136511ff9e6 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Tue, 21 Nov 2023 11:10:39 +0100 Subject: [PATCH 092/177] Small improvements and refactor --- devtools_options.yaml | 1 + .../filters/selection/selection_screen.dart | 27 +- .../servers_list/servers_list_item.dart | 555 ++++++++---------- 3 files changed, 274 insertions(+), 309 deletions(-) create mode 100644 devtools_options.yaml diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..7e7e7f6 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1 @@ +extensions: diff --git a/lib/screens/filters/selection/selection_screen.dart b/lib/screens/filters/selection/selection_screen.dart index cde1f1d..11539b2 100644 --- a/lib/screens/filters/selection/selection_screen.dart +++ b/lib/screens/filters/selection/selection_screen.dart @@ -339,19 +339,28 @@ class _Tab extends StatelessWidget { Text(text), const SizedBox(width: 8), Container( - width: 18, - height: 18, + height: 22, + padding: const EdgeInsets.symmetric( + horizontal: 2 + ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(30), color: Theme.of(context).colorScheme.primaryContainer ), - child: Text( - quantity.toString(), - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w700, - color: Theme.of(context).colorScheme.onPrimaryContainer + 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 + ), + ), ), ), ) diff --git a/lib/widgets/servers_list/servers_list_item.dart b/lib/widgets/servers_list/servers_list_item.dart index d3149c1..47ca6fe 100644 --- a/lib/widgets/servers_list/servers_list_item.dart +++ b/lib/widgets/servers_list/servers_list_item.dart @@ -5,18 +5,9 @@ import 'package:expandable/expandable.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; -import 'package:adguard_home_manager/widgets/servers_list/delete_modal.dart'; +import 'package:adguard_home_manager/widgets/servers_list/server_tile_functions.dart'; -import 'package:adguard_home_manager/models/server_status.dart'; -import 'package:adguard_home_manager/services/api_client.dart'; -import 'package:adguard_home_manager/classes/process_modal.dart'; -import 'package:adguard_home_manager/functions/snackbar.dart'; -import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; -import 'package:adguard_home_manager/models/app_log.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; -import 'package:adguard_home_manager/services/http_requests.dart'; import 'package:adguard_home_manager/models/server.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; @@ -27,12 +18,12 @@ class ServersListItem extends StatefulWidget { final void Function(int) onChange; const ServersListItem({ - Key? key, + super.key, required this.expandableController, required this.server, required this.index, required this.onChange - }) : super(key: key); + }); @override State createState() => _ServersListItemState(); @@ -71,293 +62,8 @@ class _ServersListItemState extends State with SingleTickerProv @override Widget build(BuildContext context) { - final serversProvider = Provider.of(context); - final statusProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - final width = MediaQuery.of(context).size.width; - void showDeleteModal(Server server) async { - await Future.delayed(const Duration(seconds: 0), () => { - showDialog( - context: context, - builder: (context) => DeleteModal( - serverToDelete: server, - ), - barrierDismissible: false - ) - }); - } - - void openServerModal({Server? server}) async { - await Future.delayed(const Duration(seconds: 0), (() => { - openServerFormModal(context: context, width: width, server: server) - })); - } - - void connectToServer(Server server) async { - final ProcessModal process = ProcessModal(); - process.open(AppLocalizations.of(context)!.connecting); - - final result = server.runningOnHa == true - ? await loginHA(server) - : await login(server); - - if (result['result'] == 'success') { - final ApiClientV2 apiClient2 = ApiClientV2(server: server); - serversProvider.setApiClient2(apiClient2); - serversProvider.setSelectedServer(server); - - statusProvider.setServerStatusLoad(LoadStatus.loading); - final serverStatus = await apiClient2.getServerStatus(); - if (serverStatus.successful == true) { - statusProvider.setServerStatusData( - data: serverStatus.content as ServerStatus - ); - serversProvider.checkServerUpdatesAvailable( - server: server, - ); - statusProvider.setServerStatusLoad(LoadStatus.loaded); - } - - process.close(); - } - else { - process.close(); - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.cannotConnect, - color: Colors.red - ); - } - } - - void setDefaultServer(Server server) async { - final result = await serversProvider.setDefaultServer(server); - if (result == null) { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.connectionDefaultSuccessfully, - color: Colors.green - ); - } - else { - appConfigProvider.addLog( - AppLog( - type: 'set_default_server', - dateTime: DateTime.now(), - message: result.toString() - ) - ); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.connectionDefaultFailed, - color: Colors.red - ); - } - } - - Widget leadingIcon(Server server) { - if (server.defaultServer == true) { - return Stack( - alignment: Alignment.center, - children: [ - Icon( - Icons.storage_rounded, - color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id - ? statusProvider.serverStatus != null - ? Colors.green - : Colors.orange - : null, - ), - SizedBox( - width: 25, - height: 25, - child: Stack( - alignment: Alignment.bottomRight, - children: [ - Container( - padding: const EdgeInsets.all(1), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(20) - ), - child: Icon( - Icons.star, - color: Theme.of(context).colorScheme.onPrimaryContainer, - size: 10, - ), - ), - ], - ), - ) - ], - ); - } - else { - return Icon( - Icons.storage_rounded, - color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id - ? statusProvider.serverStatus != null - ? Colors.green - : Colors.orange - : null, - ); - } - } - - Widget topRow(Server server, int index) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Row( - children: [ - Container( - margin: const EdgeInsets.only(right: 16), - child: leadingIcon(server), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "${server.connectionMethod}://${server.domain}${server.path ?? ""}${server.port != null ? ':${server.port}' : ""}", - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Column( - children: [ - const SizedBox(height: 3), - Text( - server.name, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ) - ], - ) - ], - ), - ), - ], - ), - ), - RotationTransition( - turns: animation, - child: const Icon(Icons.keyboard_arrow_down_rounded), - ), - ], - ); - } - - Widget bottomRow(Server server, int index) { - return Column( - children: [ - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - PopupMenuButton( - // color: Theme.of(context).dialogBackgroundColor, - itemBuilder: (context) => [ - PopupMenuItem( - enabled: server.defaultServer == false - ? true - : false, - onTap: server.defaultServer == false - ? (() => setDefaultServer(server)) - : null, - child: SizedBox( - child: Row( - children: [ - const Icon(Icons.star), - const SizedBox(width: 15), - Text( - server.defaultServer == true - ? AppLocalizations.of(context)!.defaultConnection - : AppLocalizations.of(context)!.setDefault, - ) - ], - ), - ) - ), - PopupMenuItem( - onTap: (() => openServerModal(server: server)), - child: Row( - children: [ - const Icon(Icons.edit), - const SizedBox(width: 15), - Text(AppLocalizations.of(context)!.edit) - ], - ) - ), - PopupMenuItem( - onTap: (() => showDeleteModal(server)), - child: Row( - children: [ - const Icon(Icons.delete), - const SizedBox(width: 15), - Text(AppLocalizations.of(context)!.delete) - ], - ) - ), - ] - ), - SizedBox( - child: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id - ? Container( - margin: const EdgeInsets.only(right: 12), - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), - decoration: BoxDecoration( - color: statusProvider.serverStatus != null - ? Colors.green - : Colors.orange, - borderRadius: BorderRadius.circular(30) - ), - child: Row( - children: [ - Icon( - statusProvider.serverStatus != null - ? Icons.check - : Icons.warning, - color: Colors.white, - ), - const SizedBox(width: 10), - Text( - statusProvider.serverStatus != null - ? AppLocalizations.of(context)!.connected - : AppLocalizations.of(context)!.selectedDisconnected, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w500 - ), - ) - ], - ), - ) - : Container( - margin: const EdgeInsets.only(right: 10), - child: TextButton( - onPressed: () => connectToServer(server), - child: Text(AppLocalizations.of(context)!.connect), - ), - ), - ) - ], - ) - ], - ); - } - return Container( decoration: BoxDecoration( border: Border( @@ -378,7 +84,7 @@ class _ServersListItemState extends State with SingleTickerProv onTap: () => widget.onChange(widget.index), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: topRow(widget.server, widget.index), + child: _TopRow(server: widget.server, animation: animation) ), ), ), @@ -390,8 +96,14 @@ class _ServersListItemState extends State with SingleTickerProv padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Column( children: [ - topRow(widget.server, widget.index), - bottomRow(widget.server, widget.index) + _TopRow(server: widget.server, animation: animation), + _BottomRow( + server: widget.server, + connectToServer: (s) => connectToServer(context: context, server: s), + openServerModal: (s) => openServerModal(context: context, server: s, width: width), + setDefaultServer: (s) => setDefaultServer(context: context, server: s), + showDeleteModal: (s) => showDeleteModal(context: context, server: s), + ) ], ), ), @@ -403,4 +115,247 @@ class _ServersListItemState extends State with SingleTickerProv ), ); } +} + +class _TopRow extends StatelessWidget { + final Server server; + final Animation animation; + + const _TopRow({ + required this.server, + required this.animation, + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + Container( + margin: const EdgeInsets.only(right: 16), + child: _LeadingIcon(server: server), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${server.connectionMethod}://${server.domain}${server.path ?? ""}${server.port != null ? ':${server.port}' : ""}", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurface + ), + ), + Column( + children: [ + const SizedBox(height: 3), + Text( + server.name, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ) + ], + ) + ], + ), + ), + ], + ), + ), + RotationTransition( + turns: animation, + child: const Icon(Icons.keyboard_arrow_down_rounded), + ), + ], + ); + } +} + +class _LeadingIcon extends StatelessWidget { + final Server server; + + const _LeadingIcon({ + required this.server, + }); + + @override + Widget build(BuildContext context) { + final serversProvider = Provider.of(context); + final statusProvider = Provider.of(context); + + if (server.defaultServer == true) { + return Stack( + alignment: Alignment.center, + children: [ + Icon( + Icons.storage_rounded, + color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id + ? statusProvider.serverStatus != null + ? Colors.green + : Colors.orange + : null, + ), + SizedBox( + width: 25, + height: 25, + child: Stack( + alignment: Alignment.bottomRight, + children: [ + Container( + padding: const EdgeInsets.all(1), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(20) + ), + child: Icon( + Icons.star, + color: Theme.of(context).colorScheme.onPrimaryContainer, + size: 10, + ), + ), + ], + ), + ) + ], + ); + } + else { + return Icon( + Icons.storage_rounded, + color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id + ? statusProvider.serverStatus != null + ? Colors.green + : Colors.orange + : null, + ); + } + } +} + +class _BottomRow extends StatelessWidget { + final Server server; + final void Function(Server) setDefaultServer; + final void Function(Server) openServerModal; + final void Function(Server) showDeleteModal; + final void Function(Server) connectToServer; + + const _BottomRow({ + required this.server, + required this.setDefaultServer, + required this.openServerModal, + required this.showDeleteModal, + required this.connectToServer, + }); + + @override + Widget build(BuildContext context) { + final serversProvider = Provider.of(context); + final statusProvider = Provider.of(context); + + return Column( + children: [ + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + PopupMenuButton( + // color: Theme.of(context).dialogBackgroundColor, + itemBuilder: (context) => [ + PopupMenuItem( + enabled: server.defaultServer == false + ? true + : false, + onTap: server.defaultServer == false + ? (() => setDefaultServer(server)) + : null, + child: SizedBox( + child: Row( + children: [ + const Icon(Icons.star), + const SizedBox(width: 15), + Text( + server.defaultServer == true + ? AppLocalizations.of(context)!.defaultConnection + : AppLocalizations.of(context)!.setDefault, + ) + ], + ), + ) + ), + PopupMenuItem( + onTap: (() => openServerModal(server)), + child: Row( + children: [ + const Icon(Icons.edit), + const SizedBox(width: 15), + Text(AppLocalizations.of(context)!.edit) + ], + ) + ), + PopupMenuItem( + onTap: (() => showDeleteModal(server)), + child: Row( + children: [ + const Icon(Icons.delete), + const SizedBox(width: 15), + Text(AppLocalizations.of(context)!.delete) + ], + ) + ), + ] + ), + SizedBox( + child: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id + ? Container( + margin: const EdgeInsets.only(right: 12), + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), + decoration: BoxDecoration( + color: statusProvider.serverStatus != null + ? Colors.green + : Colors.orange, + borderRadius: BorderRadius.circular(30) + ), + child: Row( + children: [ + Icon( + statusProvider.serverStatus != null + ? Icons.check + : Icons.warning, + color: Colors.white, + ), + const SizedBox(width: 10), + Text( + statusProvider.serverStatus != null + ? AppLocalizations.of(context)!.connected + : AppLocalizations.of(context)!.selectedDisconnected, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500 + ), + ) + ], + ), + ) + : Container( + margin: const EdgeInsets.only(right: 10), + child: TextButton( + onPressed: () => connectToServer(server), + child: Text(AppLocalizations.of(context)!.connect), + ), + ), + ) + ], + ) + ], + ); + } } \ No newline at end of file From 1ea1a016c128461dcb5694838265ed8e688bf8f1 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Tue, 21 Nov 2023 13:34:32 +0000 Subject: [PATCH 093/177] Updated Windows stuff --- windows/flutter/CMakeLists.txt | 7 ++++++- windows/innosetup_installer_builder.iss | 18 +++++++++--------- windows/runner/flutter_window.cpp | 5 +++++ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt index 930d207..903f489 100644 --- a/windows/flutter/CMakeLists.txt +++ b/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/windows/innosetup_installer_builder.iss b/windows/innosetup_installer_builder.iss index b7a348d..92a4b72 100644 --- a/windows/innosetup_installer_builder.iss +++ b/windows/innosetup_installer_builder.iss @@ -37,15 +37,15 @@ Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl" Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked [Files] -Source: "..\build\windows\runner\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\build\windows\runner\Release\dynamic_color_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\build\windows\runner\Release\flutter_windows.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\build\windows\runner\Release\sentry_flutter_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\build\windows\runner\Release\sqlite3.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\build\windows\runner\Release\sqlite3_flutter_libs_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\build\windows\runner\Release\url_launcher_windows_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\build\windows\runner\Release\window_size_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\build\windows\runner\Release\data\*"; DestDir: "{app}\data"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "..\build\windows\x64\runner\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\build\windows\x64\runner\Release\dynamic_color_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\build\windows\x64\runner\Release\flutter_windows.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\build\windows\x64\runner\Release\sentry_flutter_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\build\windows\x64\runner\Release\sqlite3.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\build\windows\x64\runner\Release\sqlite3_flutter_libs_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\build\windows\x64\runner\Release\url_launcher_windows_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\build\windows\x64\runner\Release\window_size_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\build\windows\x64\runner\Release\data\*"; DestDir: "{app}\data"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Icons] diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp index b25e363..955ee30 100644 --- a/windows/runner/flutter_window.cpp +++ b/windows/runner/flutter_window.cpp @@ -31,6 +31,11 @@ bool FlutterWindow::OnCreate() { this->Show(); }); + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + return true; } From 75376c1873691b6781c282e482baf2c394c76a58 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 01:05:18 +0100 Subject: [PATCH 094/177] Fixes and improvements --- lib/functions/check_app_updates.dart | 4 +- lib/providers/servers_provider.dart | 7 ++- .../clients/client/logs_list_client.dart | 47 +++++++------- lib/services/api_client.dart | 13 ++-- lib/services/external_requests.dart | 62 ++++++------------- lib/widgets/layout.dart | 2 + pubspec.lock | 2 +- pubspec.yaml | 1 + 8 files changed, 60 insertions(+), 78 deletions(-) diff --git a/lib/functions/check_app_updates.dart b/lib/functions/check_app_updates.dart index 7ab8352..e002556 100644 --- a/lib/functions/check_app_updates.dart +++ b/lib/functions/check_app_updates.dart @@ -14,7 +14,7 @@ Future checkAppUpdates({ }) async { var result = isBeta ? await ExternalRequests.getReleasesGitHub() - : await ExternalRequests.getLatestReleaseGitHub(); + : await ExternalRequests.getReleaseData(); if (result.successful == true) { late GitHubRelease gitHubRelease; @@ -30,7 +30,7 @@ Future checkAppUpdates({ gitHubRelease: gitHubRelease, isBeta: isBeta ); - +print(update); if (update == true) { setUpdateAvailable(gitHubRelease); diff --git a/lib/providers/servers_provider.dart b/lib/providers/servers_provider.dart index a8f1003..2cc634a 100644 --- a/lib/providers/servers_provider.dart +++ b/lib/providers/servers_provider.dart @@ -3,6 +3,7 @@ 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'; @@ -188,9 +189,9 @@ class ServersProvider with ChangeNotifier { final result = await client!.checkServerUpdates(); if (result.successful == true) { UpdateAvailableData data = UpdateAvailableData.fromJson(result.content); - final gitHubResult = await ExternalRequests.getUpdateChangelog(releaseTag: data.newVersion ?? data.currentVersion); + final gitHubResult = await ExternalRequests.getReleaseData(releaseTag: data.newVersion ?? data.currentVersion); if (gitHubResult.successful == true) { - data.changelog = gitHubResult.content; + data.changelog = (gitHubResult.content as GitHubRelease).body; } setUpdateAvailableData(data); setUpdateAvailableLoadStatus(LoadStatus.loaded, true); @@ -262,7 +263,7 @@ class ServersProvider with ChangeNotifier { if (result.successful == true) { UpdateAvailableData data = UpdateAvailableData.fromJsonUpdate(result.content); if (data.currentVersion == data.newVersion) { - final gitHubResult = await ExternalRequests.getUpdateChangelog(releaseTag: data.newVersion ?? data.currentVersion); + final gitHubResult = await ExternalRequests.getReleaseData(releaseTag: data.newVersion ?? data.currentVersion); if (gitHubResult.successful == true) { data.changelog = gitHubResult.content; } diff --git a/lib/screens/clients/client/logs_list_client.dart b/lib/screens/clients/client/logs_list_client.dart index 422c80c..bd4d2af 100644 --- a/lib/screens/clients/client/logs_list_client.dart +++ b/lib/screens/clients/client/logs_list_client.dart @@ -8,6 +8,7 @@ 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'; @@ -21,13 +22,13 @@ class LogsListClient extends StatefulWidget { final bool splitView; const LogsListClient({ - Key? key, + super.key, required this.ip, this.name, required this.serversProvider, required this.appConfigProvider, required this.splitView, - }) : super(key: key); + }); @override State createState() => _LogsListClientState(); @@ -74,32 +75,28 @@ class _LogsListClientState extends State { ) ); - final result = await cancelableRequest?.value; + final result = await cancelableRequest?.value as ApiResponse; + if (!mounted) return; - if (result != null) { - if (loadingMore != null && loadingMore == true && mounted) { - setState(() => isLoadingMore = false); - } + 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']); - } + 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); } } diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index a957fc3..d8ef244 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -689,10 +689,15 @@ class ApiClientV2 { server: server, body: data, ); - return ApiResponse( - successful: result.successful, - content: result.body != null ? jsonDecode(result.body!) : null - ); + try { + return ApiResponse( + successful: result.successful, + content: result.body != null ? jsonDecode(result.body!) : null + ); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } } Future saveEncryptionSettings({ diff --git a/lib/services/external_requests.dart b/lib/services/external_requests.dart index b63b6fb..bd467fb 100644 --- a/lib/services/external_requests.dart +++ b/lib/services/external_requests.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'dart:io'; +import 'package:http/http.dart' as http; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:adguard_home_manager/models/github_release.dart'; @@ -8,72 +8,48 @@ import 'package:adguard_home_manager/constants/urls.dart'; import 'package:adguard_home_manager/services/api_client.dart'; class ExternalRequests { - static Future getUpdateChangelog({ - required String releaseTag - }) async { - try { - HttpClient httpClient = HttpClient(); - HttpClientRequest request = await httpClient.getUrl(Uri.parse("${Urls.adGuardHomeReleasesTags}/$releaseTag")); - HttpClientResponse response = await request.close(); - String reply = await response.transform(utf8.decoder).join(); - httpClient.close(); - if (response.statusCode == 200) { - return ApiResponse( - successful: true, - content: jsonDecode(reply)['body'], - statusCode: response.statusCode - ); - } - else { - return const ApiResponse(successful: false); - } - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); - return const ApiResponse(successful: false); - } - } - static Future getReleasesGitHub() async { try { - HttpClient httpClient = HttpClient(); - HttpClientRequest request = await httpClient.getUrl(Uri.parse(Urls.getReleasesGitHub)); - HttpClientResponse response = await request.close(); - String reply = await response.transform(utf8.decoder).join(); - httpClient.close(); + final response = await http.get(Uri.parse(Urls.getReleasesGitHub)); if (response.statusCode == 200) { return ApiResponse( successful: true, - content: List.from(jsonDecode(reply).map((entry) => GitHubRelease.fromJson(entry))) + content: List.from( + jsonDecode(response.body).map((entry) => GitHubRelease.fromJson(entry)) + ) ); } else { return const ApiResponse(successful: false); } - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } catch (e) { return const ApiResponse(successful: false); } } - static Future getLatestReleaseGitHub() async { + static Future getReleaseData({ + // If releaseTag is null gets latest release + String? releaseTag + }) async { try { - HttpClient httpClient = HttpClient(); - HttpClientRequest request = await httpClient.getUrl(Uri.parse(Urls.getLatestReleaseGitHub)); - HttpClientResponse response = await request.close(); - String reply = await response.transform(utf8.decoder).join(); - httpClient.close(); + final response = await http.get( + Uri.parse( + releaseTag != null + ? "${Urls.adGuardHomeReleasesTags}/$releaseTag" + : Urls.getLatestReleaseGitHub + ) + ); if (response.statusCode == 200) { return ApiResponse( successful: true, - content: GitHubRelease.fromJson(jsonDecode(reply)), + content: GitHubRelease.fromJson(jsonDecode(response.body)), statusCode: response.statusCode ); } else { return const ApiResponse(successful: false); } - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } catch (e) { return const ApiResponse(successful: false); } } diff --git a/lib/widgets/layout.dart b/lib/widgets/layout.dart index 4d6e131..9a42415 100644 --- a/lib/widgets/layout.dart +++ b/lib/widgets/layout.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:animations/animations.dart'; import 'package:provider/provider.dart'; @@ -36,6 +37,7 @@ class _LayoutState extends State with WidgetsBindingObserver { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) async { + if (kDebugMode) return; // Don't check for app updates on debug mode final appConfigProvider = Provider.of(context, listen: false); final result = await checkAppUpdates( currentBuildNumber: appConfigProvider.getAppInfo!.buildNumber, diff --git a/pubspec.lock b/pubspec.lock index b32374c..0582275 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -319,7 +319,7 @@ packages: source: hosted version: "0.15.4" http: - dependency: transitive + dependency: "direct main" description: name: http sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" diff --git a/pubspec.yaml b/pubspec.yaml index 482ca92..bc0da25 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -75,6 +75,7 @@ dependencies: flutter_reorderable_list: ^1.3.1 pie_chart: ^5.4.0 segmented_button_slide: ^1.0.4 + http: ^1.1.0 dev_dependencies: flutter_test: From 2301848b254c37b838cd32f3ba147ab0881cf02a Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 01:27:26 +0100 Subject: [PATCH 095/177] Updated workflow --- .github/workflows/release-beta.yaml | 32 ++++++++++++++--------------- pubspec.yaml | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release-beta.yaml b/.github/workflows/release-beta.yaml index 472e119..b46f9c6 100644 --- a/.github/workflows/release-beta.yaml +++ b/.github/workflows/release-beta.yaml @@ -2,19 +2,7 @@ name: Compile and release beta build on: workflow_dispatch: - inputs: - version: - description: "Version" - required: true - default: "1.0.0" - beta-number: - description: "Beta number" - required: true - default: "1" - number: - description: "Build number" - required: true - default: "1" + jobs: build-android: name: Build Android .apk and .aab @@ -22,7 +10,6 @@ jobs: env: ANDROID_AAB_RELEASE_PATH: build/app/outputs/bundle/release ANDROID_APK_RELEASE_PATH: build/app/outputs/apk/release - VERSION_NAME: ${{ github.event.inputs.version }}-beta.${{ github.event.inputs.beta-number }} steps: - uses: actions/checkout@v3 with: @@ -33,8 +20,21 @@ jobs: run: echo "${{ secrets.KEY_PROPERTIES }}" | base64 --decode > android/key.properties - name: Decode .env run: echo "${{ secrets.ENV }}" | base64 --decode > .env - - name: Update version in YAML - run: sed -i 's/99.99.99+99/${{ env.VERSION_NAME }}+${{ github.event.inputs.number }}/g' pubspec.yaml + - name: Read pubspec.yaml + uses: pietrobolcato/action-read-yaml@1.0.0 + id: read_pubspec + with: + config: ${{ github.workspace }}/pubspec.yaml + - name: Save version on env variable + run: | + version="${{ steps.read_pubspec.outputs['version'] }}" + echo ${version} + split=$((${version//+// })) + echo ${spliy} + name=${split[0]} + number=${split[1]} + echo "VERSION_NAME=$(echo ${name}_\(${number}\))" + echo "VERSION_NAME=$(echo ${name}_\(${number}\))" >> $GITHUB_ENV - name: Update KeyStore password in gradle properties run: sed -i 's/#{KEYSTORE_PASS}#/${{ secrets.KEYSTORE_PASS }}/g' android/key.properties - name: Update KeyStore key password in gradle properties diff --git a/pubspec.yaml b/pubspec.yaml index bc0da25..9ade92d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 99.99.99+99 +version: 2.11.1+106 environment: sdk: '>=2.18.1 <3.0.0' From da5b1f84da84287f2f83c990d9a27a6f805dd0da Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 01:30:58 +0100 Subject: [PATCH 096/177] Updated workflow --- .github/workflows/release-beta.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-beta.yaml b/.github/workflows/release-beta.yaml index b46f9c6..45170d6 100644 --- a/.github/workflows/release-beta.yaml +++ b/.github/workflows/release-beta.yaml @@ -21,13 +21,14 @@ jobs: - name: Decode .env run: echo "${{ secrets.ENV }}" | base64 --decode > .env - name: Read pubspec.yaml - uses: pietrobolcato/action-read-yaml@1.0.0 + uses: jbutcher5/read-yaml@1.6 id: read_pubspec with: - config: ${{ github.workspace }}/pubspec.yaml + file: './pubspec.yaml' + key-path: '["version"]' - name: Save version on env variable run: | - version="${{ steps.read_pubspec.outputs['version'] }}" + version="${{ steps.read_pubspec.outputs.data }}" echo ${version} split=$((${version//+// })) echo ${spliy} From fbee4f61650f1689d95c1bb07a956238ccec015d Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 01:33:08 +0100 Subject: [PATCH 097/177] Updated workflow --- .github/workflows/release-beta.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-beta.yaml b/.github/workflows/release-beta.yaml index 45170d6..53bddeb 100644 --- a/.github/workflows/release-beta.yaml +++ b/.github/workflows/release-beta.yaml @@ -30,7 +30,7 @@ jobs: run: | version="${{ steps.read_pubspec.outputs.data }}" echo ${version} - split=$((${version//+// })) + split=$((${version//+/ })) echo ${spliy} name=${split[0]} number=${split[1]} From 8f4f0645ef67a74c626fcf2532d788d7aa4354a4 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 01:35:23 +0100 Subject: [PATCH 098/177] Updated workflow --- .github/workflows/release-beta.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-beta.yaml b/.github/workflows/release-beta.yaml index 53bddeb..e169850 100644 --- a/.github/workflows/release-beta.yaml +++ b/.github/workflows/release-beta.yaml @@ -31,7 +31,7 @@ jobs: version="${{ steps.read_pubspec.outputs.data }}" echo ${version} split=$((${version//+/ })) - echo ${spliy} + echo "${split[0]}__${split[1]}" name=${split[0]} number=${split[1]} echo "VERSION_NAME=$(echo ${name}_\(${number}\))" From 9ffc4b1181eb924cdd5555e5c4d923d6837d7e16 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 01:41:19 +0100 Subject: [PATCH 099/177] Updated workflow --- .github/workflows/release-beta.yaml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release-beta.yaml b/.github/workflows/release-beta.yaml index e169850..aa6c497 100644 --- a/.github/workflows/release-beta.yaml +++ b/.github/workflows/release-beta.yaml @@ -26,14 +26,16 @@ jobs: with: file: './pubspec.yaml' key-path: '["version"]' + - uses: winterjung/split@v2 + id: split + with: + msg: "${{ steps.read_pubspec.outputs.data }}" + separator: "+" - name: Save version on env variable - run: | - version="${{ steps.read_pubspec.outputs.data }}" - echo ${version} - split=$((${version//+/ })) - echo "${split[0]}__${split[1]}" - name=${split[0]} - number=${split[1]} + run: | + echo "${{ steps.split.outputs._0 }}__${{ steps.split.outputs._1 }}" + name=${{ steps.split.outputs._0 }} + number=${{ steps.split.outputs._1 }} echo "VERSION_NAME=$(echo ${name}_\(${number}\))" echo "VERSION_NAME=$(echo ${name}_\(${number}\))" >> $GITHUB_ENV - name: Update KeyStore password in gradle properties From 3da4fcf0c84ce8c9f559de5c326bb19eb972dff3 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 01:48:20 +0100 Subject: [PATCH 100/177] Updated workflow --- .github/workflows/release-beta.yaml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release-beta.yaml b/.github/workflows/release-beta.yaml index aa6c497..a6b7cd5 100644 --- a/.github/workflows/release-beta.yaml +++ b/.github/workflows/release-beta.yaml @@ -26,18 +26,15 @@ jobs: with: file: './pubspec.yaml' key-path: '["version"]' - - uses: winterjung/split@v2 - id: split + - name: Split version string + uses: winterjung/split@v2 + id: splitted_version with: msg: "${{ steps.read_pubspec.outputs.data }}" separator: "+" - name: Save version on env variable run: | - echo "${{ steps.split.outputs._0 }}__${{ steps.split.outputs._1 }}" - name=${{ steps.split.outputs._0 }} - number=${{ steps.split.outputs._1 }} - echo "VERSION_NAME=$(echo ${name}_\(${number}\))" - echo "VERSION_NAME=$(echo ${name}_\(${number}\))" >> $GITHUB_ENV + echo "VERSION_NAME=$(echo ${{ steps.splitted_version.outputs._0 }}" >> $GITHUB_ENV - name: Update KeyStore password in gradle properties run: sed -i 's/#{KEYSTORE_PASS}#/${{ secrets.KEYSTORE_PASS }}/g' android/key.properties - name: Update KeyStore key password in gradle properties From 14a62c36efb09f6642c0ab9f829203a636d969be Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 01:50:34 +0100 Subject: [PATCH 101/177] Updated workflow --- .github/workflows/release-beta.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/release-beta.yaml b/.github/workflows/release-beta.yaml index a6b7cd5..71b74a5 100644 --- a/.github/workflows/release-beta.yaml +++ b/.github/workflows/release-beta.yaml @@ -33,8 +33,7 @@ jobs: msg: "${{ steps.read_pubspec.outputs.data }}" separator: "+" - name: Save version on env variable - run: | - echo "VERSION_NAME=$(echo ${{ steps.splitted_version.outputs._0 }}" >> $GITHUB_ENV + run: echo "VERSION_NAME=$(echo ${{ steps.splitted_version.outputs._0 }})" >> $GITHUB_ENV - name: Update KeyStore password in gradle properties run: sed -i 's/#{KEYSTORE_PASS}#/${{ secrets.KEYSTORE_PASS }}/g' android/key.properties - name: Update KeyStore key password in gradle properties From 1321d14cdba4ca241e2fb2f8243e34ea5ba9e828 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 01:58:07 +0100 Subject: [PATCH 102/177] Updated workflow --- .github/workflows/release-beta.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release-beta.yaml b/.github/workflows/release-beta.yaml index 71b74a5..845e6e4 100644 --- a/.github/workflows/release-beta.yaml +++ b/.github/workflows/release-beta.yaml @@ -2,6 +2,8 @@ name: Compile and release beta build on: workflow_dispatch: + branches: + - beta jobs: build-android: From b2795d71c9572581fd73ca4ee64002fcd581e27d Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 02:15:10 +0100 Subject: [PATCH 103/177] Updated workflows --- .github/workflows/release-beta.yaml | 14 ++- .github/workflows/release-stable.yaml | 135 ++++++++++++++++++-------- 2 files changed, 105 insertions(+), 44 deletions(-) diff --git a/.github/workflows/release-beta.yaml b/.github/workflows/release-beta.yaml index 845e6e4..36d43d6 100644 --- a/.github/workflows/release-beta.yaml +++ b/.github/workflows/release-beta.yaml @@ -35,7 +35,10 @@ jobs: msg: "${{ steps.read_pubspec.outputs.data }}" separator: "+" - name: Save version on env variable - run: echo "VERSION_NAME=$(echo ${{ steps.splitted_version.outputs._0 }})" >> $GITHUB_ENV + run: | + echo "VERSION_NAME=$(echo ${{ steps.splitted_version.outputs._0 }})" >> $GITHUB_ENV + echo "::set-output name=version_name::${{ steps.splitted_version.outputs._0 }}" + echo "::set-output name=version_number::${{ steps.splitted_version.outputs._1 }}" - name: Update KeyStore password in gradle properties run: sed -i 's/#{KEYSTORE_PASS}#/${{ secrets.KEYSTORE_PASS }}/g' android/key.properties - name: Update KeyStore key password in gradle properties @@ -70,8 +73,9 @@ jobs: name: Release beta build to GitHub runs-on: ubuntu-latest needs: [build-android] - env: - VERSION_NAME: ${{ github.event.inputs.version }}-beta.${{ github.event.inputs.beta-number }} + env: + VERSION_NAME: ${{ needs.build-android.outputs.version_name }} + VERSION_NUMBER: ${{ needs.build-android.outputs.version_number }} steps: - uses: actions/checkout@v3 with: @@ -88,7 +92,7 @@ jobs: with: artifacts: "releases/*" token: ${{ secrets.GH_TOKEN }} - tag: '${{ env.VERSION_NAME }}_(${{ github.event.inputs.number }})' + tag: '${{ env.VERSION_NAME }}_(${{ env.VERSION_NUMBER }})' name: v${{ env.VERSION_NAME }} draft: true prerelease: true @@ -98,7 +102,7 @@ jobs: runs-on: ubuntu-latest needs: [build-android] env: - VERSION_NAME: ${{ github.event.inputs.version }}-beta.${{ github.event.inputs.beta-number }} + VERSION_NAME: ${{ needs.build-android.outputs.version_name }} steps: - uses: actions/checkout@v3 with: diff --git a/.github/workflows/release-stable.yaml b/.github/workflows/release-stable.yaml index b8d2b73..18da011 100644 --- a/.github/workflows/release-stable.yaml +++ b/.github/workflows/release-stable.yaml @@ -2,15 +2,8 @@ name: Compile and release production build on: workflow_dispatch: - inputs: - version: - description: "Version" - required: true - default: "1.0.0" - number: - description: "Build number" - required: true - default: "1" + branches: + - master jobs: build-android: name: Build Android .apk and .aab @@ -26,8 +19,23 @@ jobs: run: echo "${{ secrets.KEY_PROPERTIES }}" | base64 --decode > android/key.properties - name: Decode .env run: echo "${{ secrets.ENV }}" | base64 --decode > .env - - name: Update version in YAML - run: sed -i 's/99.99.99+99/${{ github.event.inputs.version }}+${{ github.event.inputs.number }}/g' pubspec.yaml + - name: Read pubspec.yaml + uses: jbutcher5/read-yaml@1.6 + id: read_pubspec + with: + file: './pubspec.yaml' + key-path: '["version"]' + - name: Split version string + uses: winterjung/split@v2 + id: splitted_version + with: + msg: "${{ steps.read_pubspec.outputs.data }}" + separator: "+" + - name: Save version on env variable + run: | + echo "VERSION_NAME=$(echo ${{ steps.splitted_version.outputs._0 }})" >> $GITHUB_ENV + echo "::set-output name=version_name::${{ steps.splitted_version.outputs._0 }}" + echo "::set-output name=version_number::${{ steps.splitted_version.outputs._1 }}" - name: Update KeyStore password in gradle properties run: sed -i 's/#{KEYSTORE_PASS}#/${{ secrets.KEYSTORE_PASS }}/g' android/key.properties - name: Update KeyStore key password in gradle properties @@ -44,20 +52,20 @@ jobs: - run: flutter build apk --release - run: flutter build appbundle --release - name: Rename apk - run: mv $ANDROID_APK_RELEASE_PATH/app-release.apk $ANDROID_APK_RELEASE_PATH/AdGuardHomeManager_${{ github.event.inputs.version }}_Android.apk + run: mv $ANDROID_APK_RELEASE_PATH/app-release.apk $ANDROID_APK_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk - name: Rename aab - run: mv $ANDROID_AAB_RELEASE_PATH/app-release.aab $ANDROID_AAB_RELEASE_PATH/AdGuardHomeManager_${{ github.event.inputs.version }}_Android.aab + run: mv $ANDROID_AAB_RELEASE_PATH/app-release.aab $ANDROID_AAB_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab - name: Copy apk to project root - run: cp $ANDROID_APK_RELEASE_PATH/AdGuardHomeManager_${{ github.event.inputs.version }}_Android.apk AdGuardHomeManager_${{ github.event.inputs.version }}_Android.apk + run: cp $ANDROID_APK_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk - name: Copy aab to project root - run: cp $ANDROID_AAB_RELEASE_PATH/AdGuardHomeManager_${{ github.event.inputs.version }}_Android.aab AdGuardHomeManager_${{ github.event.inputs.version }}_Android.aab + run: cp $ANDROID_AAB_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab - name: Upload artifact uses: actions/upload-artifact@v3 with: name: android path: | - AdGuardHomeManager_${{ github.event.inputs.version }}_Android.aab - AdGuardHomeManager_${{ github.event.inputs.version }}_Android.apk + AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab + AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk build-macos: name: Build macOS .dmg runs-on: macos-latest @@ -67,8 +75,23 @@ jobs: - uses: actions/checkout@v3 - name: Decode .env run: echo "${{ secrets.ENV }}" | base64 --decode > .env - - name: Update version in YAML - run: sed -i '' 's/99.99.99+99/${{ github.event.inputs.version }}+${{ github.event.inputs.number }}/g' pubspec.yaml + - name: Read pubspec.yaml + uses: jbutcher5/read-yaml@1.6 + id: read_pubspec + with: + file: './pubspec.yaml' + key-path: '["version"]' + - name: Split version string + uses: winterjung/split@v2 + id: splitted_version + with: + msg: "${{ steps.read_pubspec.outputs.data }}" + separator: "+" + - name: Save version on env variable + run: | + echo "VERSION_NAME=$(echo ${{ steps.splitted_version.outputs._0 }})" >> $GITHUB_ENV + echo "::set-output name=version_name::${{ steps.splitted_version.outputs._0 }}" + echo "::set-output name=version_number::${{ steps.splitted_version.outputs._1 }}" - uses: subosito/flutter-action@v2 with: channel: "stable" @@ -96,14 +119,14 @@ jobs: - name: Generate symbolic link to Applications dir run: ln -s /Applications $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager - name: Generate dmg - run: hdiutil create -srcfolder $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager $MACOS_APP_RELEASE_PATH/AdGuardHomeManager_${{ github.event.inputs.version }}_macOS_Universal.dmg + run: hdiutil create -srcfolder $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager $MACOS_APP_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_macOS_Universal.dmg - name: Copy dmg to project root - run: cp $MACOS_APP_RELEASE_PATH/AdGuardHomeManager_${{ github.event.inputs.version }}_macOS_Universal.dmg AdGuardHomeManager_${{ github.event.inputs.version }}_macOS_Universal.dmg + run: cp $MACOS_APP_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_macOS_Universal.dmg AdGuardHomeManager_${{ env.VERSION_NAME }}_macOS_Universal.dmg - name: Upload artifact uses: actions/upload-artifact@v3 with: name: macos - path: AdGuardHomeManager_${{ github.event.inputs.version }}_macOS_Universal.dmg + path: AdGuardHomeManager_${{ env.VERSION_NAME }}_macOS_Universal.dmg build-linux: name: Build Linux .tar.gz and .deb runs-on: ubuntu-latest @@ -111,10 +134,25 @@ jobs: - uses: actions/checkout@v3 - name: Decode .env run: echo "${{ secrets.ENV }}" | base64 --decode > .env - - name: Update version in pubspec.yaml - run: sed -i 's/99.99.99+99/${{ github.event.inputs.version }}+${{ github.event.inputs.number }}/g' pubspec.yaml + - name: Read pubspec.yaml + uses: jbutcher5/read-yaml@1.6 + id: read_pubspec + with: + file: './pubspec.yaml' + key-path: '["version"]' + - name: Split version string + uses: winterjung/split@v2 + id: splitted_version + with: + msg: "${{ steps.read_pubspec.outputs.data }}" + separator: "+" + - name: Save version on env variable + run: | + echo "VERSION_NAME=$(echo ${{ steps.splitted_version.outputs._0 }})" >> $GITHUB_ENV + echo "::set-output name=version_name::${{ steps.splitted_version.outputs._0 }}" + echo "::set-output name=version_number::${{ steps.splitted_version.outputs._1 }}" - name: Update version in debian.yaml - run: sed -i 's//${{ github.event.inputs.version }}/g' debian/debian.yaml + run: sed -i 's//${{ env.VERSION_NAME }}/g' debian/debian.yaml - name: Update dependencies list run: sudo apt-get update - name: Install dependencies @@ -130,7 +168,7 @@ jobs: - name: Generate .deb package run: flutter_to_debian - name: Move .deb package to project root - run: mv debian/packages/AdGuardHomeManager_${{ github.event.inputs.version }}_amd64.deb AdGuardHomeManager_${{ github.event.inputs.version }}_Linux_amd64.deb + run: mv debian/packages/AdGuardHomeManager_${{ env.VERSION_NAME }}_amd64.deb AdGuardHomeManager_${{ env.VERSION_NAME }}_Linux_amd64.deb - name: Generate .tar.gz package uses: a7ul/tar-action@v1.1.3 id: compress @@ -141,14 +179,14 @@ jobs: ./data ./lib ./AdGuardHomeManager - outPath: AdGuardHomeManager_${{ github.event.inputs.version }}_Linux.tar.gz + outPath: AdGuardHomeManager_${{ env.VERSION_NAME }}_Linux.tar.gz - name: Upload artifact uses: actions/upload-artifact@v3 with: name: linux path: | - AdGuardHomeManager_${{ github.event.inputs.version }}_Linux_amd64.deb - AdGuardHomeManager_${{ github.event.inputs.version }}_Linux.tar.gz + AdGuardHomeManager_${{ env.VERSION_NAME }}_Linux_amd64.deb + AdGuardHomeManager_${{ env.VERSION_NAME }}_Linux.tar.gz build-windows: name: Build Windows installer runs-on: windows-latest @@ -158,13 +196,27 @@ jobs: shell: pwsh run: | [IO.File]::WriteAllBytes('.env', [Convert]::FromBase64String('${{ secrets.ENV }}')) - - name: Update version in pubspec.yaml - shell: bash - run: sed -i 's/99.99.99+99/${{ github.event.inputs.version }}+${{ github.event.inputs.number }}/g' pubspec.yaml + - name: Read pubspec.yaml + uses: jbutcher5/read-yaml@1.6 + id: read_pubspec + with: + file: './pubspec.yaml' + key-path: '["version"]' + - name: Split version string + uses: winterjung/split@v2 + id: splitted_version + with: + msg: "${{ steps.read_pubspec.outputs.data }}" + separator: "+" + - name: Save version on env variable + run: | + echo "VERSION_NAME=$(echo ${{ steps.splitted_version.outputs._0 }})" >> $GITHUB_ENV + echo "::set-output name=version_name::${{ steps.splitted_version.outputs._0 }}" + echo "::set-output name=version_number::${{ steps.splitted_version.outputs._1 }}" - name: Update version in innosetup config file shell: pwsh run: | - (Get-Content windows/innosetup_installer_builder.iss) -replace '', '${{ github.event.inputs.version }}' | Out-File -encoding ASCII windows/innosetup_installer_builder.iss + (Get-Content windows/innosetup_installer_builder.iss) -replace '', '${{ env.VERSION_NAME }}' | Out-File -encoding ASCII windows/innosetup_installer_builder.iss - uses: subosito/flutter-action@v2 with: channel: "stable" @@ -174,16 +226,19 @@ jobs: - name: Build installer witn innosetup run: iscc /Q windows/innosetup_installer_builder.iss - name: Move installer file to root directory - run: move build/windows/aghm_installer.exe AdGuardHomeManager_${{ github.event.inputs.version }}_Windows_x64.exe + run: move build/windows/aghm_installer.exe AdGuardHomeManager_${{ env.VERSION_NAME }}_Windows_x64.exe - name: Upload artifact uses: actions/upload-artifact@v3 with: name: windows - path: AdGuardHomeManager_${{ github.event.inputs.version }}_Windows_x64.exe + path: AdGuardHomeManager_${{ env.VERSION_NAME }}_Windows_x64.exe release-builds-github: name: Release builds to GitHub runs-on: ubuntu-latest needs: [build-android, build-macos, build-linux, build-windows] + env: + VERSION_NAME: ${{ needs.build-android.outputs.version_name }} + VERSION_NUMBER: ${{ needs.build-android.outputs.version_number }} steps: - uses: actions/checkout@v3 - name: Create builds directory @@ -213,8 +268,8 @@ jobs: with: artifacts: "releases/*" token: ${{ secrets.GH_TOKEN }} - tag: '${{ github.event.inputs.version }}_(${{ github.event.inputs.number }})' - name: v${{ github.event.inputs.version }} + tag: '${{ env.VERSION_NAME }}_(${{ env.VERSION_NUMBER }})' + name: v${{ env.VERSION_NAME }} draft: true prerelease: false commit: ${{ github.sha }} @@ -222,6 +277,8 @@ jobs: name: Release Android build to the Google Play Store runs-on: ubuntu-latest needs: [build-android, build-macos, build-linux, build-windows] + env: + VERSION_NAME: ${{ needs.build-android.outputs.version_name }} steps: - uses: actions/checkout@v3 - name: Download Android artifacts @@ -233,7 +290,7 @@ jobs: with: serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_ACCOUNT_KEY }} packageName: com.jgeek00.adguard_home_manager - releaseFiles: AdGuardHomeManager_${{ github.event.inputs.version }}_Android.aab + releaseFiles: AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab track: production status: draft - releaseName: ${{ github.event.inputs.version }} \ No newline at end of file + releaseName: ${{ env.VERSION_NAME }} \ No newline at end of file From 42cfe4b4a6a065a4531a8b9514c6671d6b997bf5 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 02:25:02 +0100 Subject: [PATCH 104/177] Updated workflow --- .github/workflows/release-beta.yaml | 15 +++---- .github/workflows/release-stable.yaml | 61 +++++++++++---------------- 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/.github/workflows/release-beta.yaml b/.github/workflows/release-beta.yaml index 36d43d6..93b09b1 100644 --- a/.github/workflows/release-beta.yaml +++ b/.github/workflows/release-beta.yaml @@ -28,17 +28,14 @@ jobs: with: file: './pubspec.yaml' key-path: '["version"]' - - name: Split version string - uses: winterjung/split@v2 - id: splitted_version - with: - msg: "${{ steps.read_pubspec.outputs.data }}" - separator: "+" - name: Save version on env variable run: | - echo "VERSION_NAME=$(echo ${{ steps.splitted_version.outputs._0 }})" >> $GITHUB_ENV - echo "::set-output name=version_name::${{ steps.splitted_version.outputs._0 }}" - echo "::set-output name=version_number::${{ steps.splitted_version.outputs._1 }}" + version=${{ steps.read_pubspec.outputs.data }} + IFS='+' + read -r -a split <<< "$version" + echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV + echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_OUTPUT + echo "VERSION_NUMBER=$(echo ${split[1]})" >> $GITHUB_OUTPUT - name: Update KeyStore password in gradle properties run: sed -i 's/#{KEYSTORE_PASS}#/${{ secrets.KEYSTORE_PASS }}/g' android/key.properties - name: Update KeyStore key password in gradle properties diff --git a/.github/workflows/release-stable.yaml b/.github/workflows/release-stable.yaml index 18da011..6611233 100644 --- a/.github/workflows/release-stable.yaml +++ b/.github/workflows/release-stable.yaml @@ -25,17 +25,14 @@ jobs: with: file: './pubspec.yaml' key-path: '["version"]' - - name: Split version string - uses: winterjung/split@v2 - id: splitted_version - with: - msg: "${{ steps.read_pubspec.outputs.data }}" - separator: "+" - name: Save version on env variable run: | - echo "VERSION_NAME=$(echo ${{ steps.splitted_version.outputs._0 }})" >> $GITHUB_ENV - echo "::set-output name=version_name::${{ steps.splitted_version.outputs._0 }}" - echo "::set-output name=version_number::${{ steps.splitted_version.outputs._1 }}" + version=${{ steps.read_pubspec.outputs.data }} + IFS='+' + read -r -a split <<< "$version" + echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV + echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_OUTPUT + echo "VERSION_NUMBER=$(echo ${split[1]})" >> $GITHUB_OUTPUT - name: Update KeyStore password in gradle properties run: sed -i 's/#{KEYSTORE_PASS}#/${{ secrets.KEYSTORE_PASS }}/g' android/key.properties - name: Update KeyStore key password in gradle properties @@ -81,17 +78,14 @@ jobs: with: file: './pubspec.yaml' key-path: '["version"]' - - name: Split version string - uses: winterjung/split@v2 - id: splitted_version - with: - msg: "${{ steps.read_pubspec.outputs.data }}" - separator: "+" - name: Save version on env variable run: | - echo "VERSION_NAME=$(echo ${{ steps.splitted_version.outputs._0 }})" >> $GITHUB_ENV - echo "::set-output name=version_name::${{ steps.splitted_version.outputs._0 }}" - echo "::set-output name=version_number::${{ steps.splitted_version.outputs._1 }}" + version=${{ steps.read_pubspec.outputs.data }} + IFS='+' + read -r -a split <<< "$version" + echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV + echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_OUTPUT + echo "VERSION_NUMBER=$(echo ${split[1]})" >> $GITHUB_OUTPUT - uses: subosito/flutter-action@v2 with: channel: "stable" @@ -140,17 +134,14 @@ jobs: with: file: './pubspec.yaml' key-path: '["version"]' - - name: Split version string - uses: winterjung/split@v2 - id: splitted_version - with: - msg: "${{ steps.read_pubspec.outputs.data }}" - separator: "+" - name: Save version on env variable run: | - echo "VERSION_NAME=$(echo ${{ steps.splitted_version.outputs._0 }})" >> $GITHUB_ENV - echo "::set-output name=version_name::${{ steps.splitted_version.outputs._0 }}" - echo "::set-output name=version_number::${{ steps.splitted_version.outputs._1 }}" + version=${{ steps.read_pubspec.outputs.data }} + IFS='+' + read -r -a split <<< "$version" + echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV + echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_OUTPUT + echo "VERSION_NUMBER=$(echo ${split[1]})" >> $GITHUB_OUTPUT - name: Update version in debian.yaml run: sed -i 's//${{ env.VERSION_NAME }}/g' debian/debian.yaml - name: Update dependencies list @@ -202,17 +193,15 @@ jobs: with: file: './pubspec.yaml' key-path: '["version"]' - - name: Split version string - uses: winterjung/split@v2 - id: splitted_version - with: - msg: "${{ steps.read_pubspec.outputs.data }}" - separator: "+" - name: Save version on env variable + shell: bash run: | - echo "VERSION_NAME=$(echo ${{ steps.splitted_version.outputs._0 }})" >> $GITHUB_ENV - echo "::set-output name=version_name::${{ steps.splitted_version.outputs._0 }}" - echo "::set-output name=version_number::${{ steps.splitted_version.outputs._1 }}" + version=${{ steps.read_pubspec.outputs.data }} + IFS='+' + read -r -a split <<< "$version" + echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV + echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_OUTPUT + echo "VERSION_NUMBER=$(echo ${split[1]})" >> $GITHUB_OUTPUT - name: Update version in innosetup config file shell: pwsh run: | From 3adbc769027451dc7dae678ab44b0819476669ea Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 02:43:48 +0100 Subject: [PATCH 105/177] Updated workflows --- .github/workflows/release-beta.yaml | 14 ++++++---- .github/workflows/release-stable.yaml | 38 +++++++++++++++++++-------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release-beta.yaml b/.github/workflows/release-beta.yaml index 93b09b1..792353e 100644 --- a/.github/workflows/release-beta.yaml +++ b/.github/workflows/release-beta.yaml @@ -12,6 +12,9 @@ jobs: env: ANDROID_AAB_RELEASE_PATH: build/app/outputs/bundle/release ANDROID_APK_RELEASE_PATH: build/app/outputs/apk/release + outputs: + VERSION_NAME: ${{ steps.save_version.outputs.version_name }} + VERSION_NUMBER: ${{ steps.save_version.outputs.version_number }} steps: - uses: actions/checkout@v3 with: @@ -29,13 +32,14 @@ jobs: file: './pubspec.yaml' key-path: '["version"]' - name: Save version on env variable + id: save_version run: | version=${{ steps.read_pubspec.outputs.data }} IFS='+' read -r -a split <<< "$version" echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV - echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_OUTPUT - echo "VERSION_NUMBER=$(echo ${split[1]})" >> $GITHUB_OUTPUT + echo "version_name=${split[0]}" >> $GITHUB_OUTPUT + echo "version_number=${split[1]}" >> $GITHUB_OUTPUT - name: Update KeyStore password in gradle properties run: sed -i 's/#{KEYSTORE_PASS}#/${{ secrets.KEYSTORE_PASS }}/g' android/key.properties - name: Update KeyStore key password in gradle properties @@ -71,8 +75,8 @@ jobs: runs-on: ubuntu-latest needs: [build-android] env: - VERSION_NAME: ${{ needs.build-android.outputs.version_name }} - VERSION_NUMBER: ${{ needs.build-android.outputs.version_number }} + VERSION_NAME: ${{ needs.build-android.outputs.VERSION_NAME }} + VERSION_NUMBER: ${{ needs.build-android.outputs.VERSION_NUMBER }} steps: - uses: actions/checkout@v3 with: @@ -99,7 +103,7 @@ jobs: runs-on: ubuntu-latest needs: [build-android] env: - VERSION_NAME: ${{ needs.build-android.outputs.version_name }} + VERSION_NAME: ${{ needs.build-android.outputs.VERSION_NAME }} steps: - uses: actions/checkout@v3 with: diff --git a/.github/workflows/release-stable.yaml b/.github/workflows/release-stable.yaml index 6611233..f5a4ee6 100644 --- a/.github/workflows/release-stable.yaml +++ b/.github/workflows/release-stable.yaml @@ -11,6 +11,9 @@ jobs: env: ANDROID_AAB_RELEASE_PATH: build/app/outputs/bundle/release ANDROID_APK_RELEASE_PATH: build/app/outputs/apk/release + outputs: + VERSION_NAME: ${{ steps.save_version.outputs.version_name }} + VERSION_NUMBER: ${{ steps.save_version.outputs.version_number }} steps: - uses: actions/checkout@v3 - name: Decode android/app/keystore.jks @@ -26,13 +29,14 @@ jobs: file: './pubspec.yaml' key-path: '["version"]' - name: Save version on env variable + id: save_version run: | version=${{ steps.read_pubspec.outputs.data }} IFS='+' read -r -a split <<< "$version" echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV - echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_OUTPUT - echo "VERSION_NUMBER=$(echo ${split[1]})" >> $GITHUB_OUTPUT + echo "version_name=${split[0]}" >> $GITHUB_OUTPUT + echo "version_number=${split[1]}" >> $GITHUB_OUTPUT - name: Update KeyStore password in gradle properties run: sed -i 's/#{KEYSTORE_PASS}#/${{ secrets.KEYSTORE_PASS }}/g' android/key.properties - name: Update KeyStore key password in gradle properties @@ -68,6 +72,9 @@ jobs: runs-on: macos-latest env: MACOS_APP_RELEASE_PATH: build/macos/Build/Products/Release + outputs: + VERSION_NAME: ${{ steps.save_version.outputs.version_name }} + VERSION_NUMBER: ${{ steps.save_version.outputs.version_number }} steps: - uses: actions/checkout@v3 - name: Decode .env @@ -79,13 +86,14 @@ jobs: file: './pubspec.yaml' key-path: '["version"]' - name: Save version on env variable + id: save_version run: | version=${{ steps.read_pubspec.outputs.data }} IFS='+' read -r -a split <<< "$version" echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV - echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_OUTPUT - echo "VERSION_NUMBER=$(echo ${split[1]})" >> $GITHUB_OUTPUT + echo "version_name=${split[0]}" >> $GITHUB_OUTPUT + echo "version_number=${split[1]}" >> $GITHUB_OUTPUT - uses: subosito/flutter-action@v2 with: channel: "stable" @@ -124,6 +132,9 @@ jobs: build-linux: name: Build Linux .tar.gz and .deb runs-on: ubuntu-latest + outputs: + VERSION_NAME: ${{ steps.save_version.outputs.version_name }} + VERSION_NUMBER: ${{ steps.save_version.outputs.version_number }} steps: - uses: actions/checkout@v3 - name: Decode .env @@ -135,13 +146,14 @@ jobs: file: './pubspec.yaml' key-path: '["version"]' - name: Save version on env variable + id: save_version run: | version=${{ steps.read_pubspec.outputs.data }} IFS='+' read -r -a split <<< "$version" echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV - echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_OUTPUT - echo "VERSION_NUMBER=$(echo ${split[1]})" >> $GITHUB_OUTPUT + echo "version_name=${split[0]}" >> $GITHUB_OUTPUT + echo "version_number=${split[1]}" >> $GITHUB_OUTPUT - name: Update version in debian.yaml run: sed -i 's//${{ env.VERSION_NAME }}/g' debian/debian.yaml - name: Update dependencies list @@ -181,6 +193,9 @@ jobs: build-windows: name: Build Windows installer runs-on: windows-latest + outputs: + VERSION_NAME: ${{ steps.save_version.outputs.version_name }} + VERSION_NUMBER: ${{ steps.save_version.outputs.version_number }} steps: - uses: actions/checkout@v3 - name: Decode .env @@ -195,13 +210,14 @@ jobs: key-path: '["version"]' - name: Save version on env variable shell: bash + id: save_version run: | version=${{ steps.read_pubspec.outputs.data }} IFS='+' read -r -a split <<< "$version" echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV - echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_OUTPUT - echo "VERSION_NUMBER=$(echo ${split[1]})" >> $GITHUB_OUTPUT + echo "version_name=${split[0]}" >> $GITHUB_OUTPUT + echo "version_number=${split[1]}" >> $GITHUB_OUTPUT - name: Update version in innosetup config file shell: pwsh run: | @@ -226,8 +242,8 @@ jobs: runs-on: ubuntu-latest needs: [build-android, build-macos, build-linux, build-windows] env: - VERSION_NAME: ${{ needs.build-android.outputs.version_name }} - VERSION_NUMBER: ${{ needs.build-android.outputs.version_number }} + VERSION_NAME: ${{ needs.build-android.outputs.VERSION_NAME }} + VERSION_NUMBER: ${{ needs.build-android.outputs.VERSION_NUMBER }} steps: - uses: actions/checkout@v3 - name: Create builds directory @@ -267,7 +283,7 @@ jobs: runs-on: ubuntu-latest needs: [build-android, build-macos, build-linux, build-windows] env: - VERSION_NAME: ${{ needs.build-android.outputs.version_name }} + VERSION_NAME: ${{ needs.build-android.outputs.VERSION_NAME }} steps: - uses: actions/checkout@v3 - name: Download Android artifacts From fa6b95d7684d3bdf1ad4e0de3fcc78ebab6aa2c8 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 03:08:34 +0100 Subject: [PATCH 106/177] Removed import --- lib/services/external_requests.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/services/external_requests.dart b/lib/services/external_requests.dart index bd467fb..5f6ca58 100644 --- a/lib/services/external_requests.dart +++ b/lib/services/external_requests.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:http/http.dart' as http; -import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:adguard_home_manager/models/github_release.dart'; import 'package:adguard_home_manager/constants/urls.dart'; From d2c1c9431b7671611fbcdc2fe28c53425096cec6 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 03:14:02 +0100 Subject: [PATCH 107/177] Updated workflow --- .github/workflows/release-beta.yaml | 6 +++--- .github/workflows/release-stable.yaml | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release-beta.yaml b/.github/workflows/release-beta.yaml index 792353e..5eb2f0a 100644 --- a/.github/workflows/release-beta.yaml +++ b/.github/workflows/release-beta.yaml @@ -26,11 +26,11 @@ jobs: - name: Decode .env run: echo "${{ secrets.ENV }}" | base64 --decode > .env - name: Read pubspec.yaml - uses: jbutcher5/read-yaml@1.6 + uses: christian-ci/action-yaml-github-output@v2 id: read_pubspec with: - file: './pubspec.yaml' - key-path: '["version"]' + file_path: './pubspec.yaml' + main_key: version - name: Save version on env variable id: save_version run: | diff --git a/.github/workflows/release-stable.yaml b/.github/workflows/release-stable.yaml index f5a4ee6..754384b 100644 --- a/.github/workflows/release-stable.yaml +++ b/.github/workflows/release-stable.yaml @@ -23,11 +23,11 @@ jobs: - name: Decode .env run: echo "${{ secrets.ENV }}" | base64 --decode > .env - name: Read pubspec.yaml - uses: jbutcher5/read-yaml@1.6 + uses: christian-ci/action-yaml-github-output@v2 id: read_pubspec with: - file: './pubspec.yaml' - key-path: '["version"]' + file_path: './pubspec.yaml' + main_key: version - name: Save version on env variable id: save_version run: | @@ -80,11 +80,11 @@ jobs: - name: Decode .env run: echo "${{ secrets.ENV }}" | base64 --decode > .env - name: Read pubspec.yaml - uses: jbutcher5/read-yaml@1.6 + uses: christian-ci/action-yaml-github-output@v2 id: read_pubspec with: - file: './pubspec.yaml' - key-path: '["version"]' + file_path: './pubspec.yaml' + main_key: version - name: Save version on env variable id: save_version run: | @@ -140,11 +140,11 @@ jobs: - name: Decode .env run: echo "${{ secrets.ENV }}" | base64 --decode > .env - name: Read pubspec.yaml - uses: jbutcher5/read-yaml@1.6 + uses: christian-ci/action-yaml-github-output@v2 id: read_pubspec with: - file: './pubspec.yaml' - key-path: '["version"]' + file_path: './pubspec.yaml' + main_key: version - name: Save version on env variable id: save_version run: | @@ -203,11 +203,11 @@ jobs: run: | [IO.File]::WriteAllBytes('.env', [Convert]::FromBase64String('${{ secrets.ENV }}')) - name: Read pubspec.yaml - uses: jbutcher5/read-yaml@1.6 + uses: christian-ci/action-yaml-github-output@v2 id: read_pubspec with: - file: './pubspec.yaml' - key-path: '["version"]' + file_path: './pubspec.yaml' + main_key: version - name: Save version on env variable shell: bash id: save_version From 2d4733b658711f939b3303dad44b5952695335b0 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 03:16:32 +0100 Subject: [PATCH 108/177] Updated workflow --- .github/workflows/release-beta.yaml | 2 +- .github/workflows/release-stable.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-beta.yaml b/.github/workflows/release-beta.yaml index 5eb2f0a..3d5ef3d 100644 --- a/.github/workflows/release-beta.yaml +++ b/.github/workflows/release-beta.yaml @@ -29,7 +29,7 @@ jobs: uses: christian-ci/action-yaml-github-output@v2 id: read_pubspec with: - file_path: './pubspec.yaml' + file_path: ./pubspec.yaml main_key: version - name: Save version on env variable id: save_version diff --git a/.github/workflows/release-stable.yaml b/.github/workflows/release-stable.yaml index 754384b..9c5ed04 100644 --- a/.github/workflows/release-stable.yaml +++ b/.github/workflows/release-stable.yaml @@ -26,7 +26,7 @@ jobs: uses: christian-ci/action-yaml-github-output@v2 id: read_pubspec with: - file_path: './pubspec.yaml' + file_path: ./pubspec.yaml main_key: version - name: Save version on env variable id: save_version From 70c41a5b7946d06f63025ac321a7c33b6a4f9d16 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 03:25:11 +0100 Subject: [PATCH 109/177] Updated workflow --- .github/workflows/release-beta.yaml | 6 +++--- .github/workflows/release-stable.yaml | 28 +++++++++++++-------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release-beta.yaml b/.github/workflows/release-beta.yaml index 3d5ef3d..8f72ac4 100644 --- a/.github/workflows/release-beta.yaml +++ b/.github/workflows/release-beta.yaml @@ -26,11 +26,11 @@ jobs: - name: Decode .env run: echo "${{ secrets.ENV }}" | base64 --decode > .env - name: Read pubspec.yaml - uses: christian-ci/action-yaml-github-output@v2 + uses: adore-me/read-yaml@v1.0.0 id: read_pubspec with: - file_path: ./pubspec.yaml - main_key: version + file: './pubspec.yaml' + key-path: '["version"]' - name: Save version on env variable id: save_version run: | diff --git a/.github/workflows/release-stable.yaml b/.github/workflows/release-stable.yaml index 9c5ed04..f1b41c9 100644 --- a/.github/workflows/release-stable.yaml +++ b/.github/workflows/release-stable.yaml @@ -22,12 +22,12 @@ jobs: run: echo "${{ secrets.KEY_PROPERTIES }}" | base64 --decode > android/key.properties - name: Decode .env run: echo "${{ secrets.ENV }}" | base64 --decode > .env - - name: Read pubspec.yaml - uses: christian-ci/action-yaml-github-output@v2 + - name: Read pubspec.yaml + uses: adore-me/read-yaml@v1.0.0 id: read_pubspec with: - file_path: ./pubspec.yaml - main_key: version + file: './pubspec.yaml' + key-path: '["version"]' - name: Save version on env variable id: save_version run: | @@ -80,11 +80,11 @@ jobs: - name: Decode .env run: echo "${{ secrets.ENV }}" | base64 --decode > .env - name: Read pubspec.yaml - uses: christian-ci/action-yaml-github-output@v2 + uses: adore-me/read-yaml@v1.0.0 id: read_pubspec with: - file_path: './pubspec.yaml' - main_key: version + file: './pubspec.yaml' + key-path: '["version"]' - name: Save version on env variable id: save_version run: | @@ -139,12 +139,12 @@ jobs: - uses: actions/checkout@v3 - name: Decode .env run: echo "${{ secrets.ENV }}" | base64 --decode > .env - - name: Read pubspec.yaml - uses: christian-ci/action-yaml-github-output@v2 + - name: Read pubspec.yaml + uses: adore-me/read-yaml@v1.0.0 id: read_pubspec with: - file_path: './pubspec.yaml' - main_key: version + file: './pubspec.yaml' + key-path: '["version"]' - name: Save version on env variable id: save_version run: | @@ -203,11 +203,11 @@ jobs: run: | [IO.File]::WriteAllBytes('.env', [Convert]::FromBase64String('${{ secrets.ENV }}')) - name: Read pubspec.yaml - uses: christian-ci/action-yaml-github-output@v2 + uses: adore-me/read-yaml@v1.0.0 id: read_pubspec with: - file_path: './pubspec.yaml' - main_key: version + file: './pubspec.yaml' + key-path: '["version"]' - name: Save version on env variable shell: bash id: save_version From 2f32e3d3e7ecd65f81d3745c521d3056b7b4dcf7 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 03:26:47 +0100 Subject: [PATCH 110/177] Updated workflow --- .github/workflows/release-stable.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-stable.yaml b/.github/workflows/release-stable.yaml index f1b41c9..2d8f0e8 100644 --- a/.github/workflows/release-stable.yaml +++ b/.github/workflows/release-stable.yaml @@ -4,6 +4,7 @@ on: workflow_dispatch: branches: - master + jobs: build-android: name: Build Android .apk and .aab @@ -22,7 +23,7 @@ jobs: run: echo "${{ secrets.KEY_PROPERTIES }}" | base64 --decode > android/key.properties - name: Decode .env run: echo "${{ secrets.ENV }}" | base64 --decode > .env - - name: Read pubspec.yaml + - name: Read pubspec.yaml uses: adore-me/read-yaml@v1.0.0 id: read_pubspec with: @@ -139,7 +140,7 @@ jobs: - uses: actions/checkout@v3 - name: Decode .env run: echo "${{ secrets.ENV }}" | base64 --decode > .env - - name: Read pubspec.yaml + - name: Read pubspec.yaml uses: adore-me/read-yaml@v1.0.0 id: read_pubspec with: From 791182d9c40a7c08ac5dc8b9874b080324066d4f Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 23 Nov 2023 10:29:21 +0100 Subject: [PATCH 111/177] Fix format exception getServerStatus --- lib/services/api_client.dart | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index d8ef244..d99ab7c 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -59,24 +59,30 @@ class ApiClientV2 { HttpRequestClient.get(urlPath: "/parental/status", server: server), HttpRequestClient.get(urlPath: "/clients", server: server), ]); - if ( results.map((e) => e.successful).every((e) => e == true) && results.map((e) => e.body).every((e) => e != null) ) { - final Map mappedData = { - 'stats': jsonDecode(results[0].body!), - 'clients': jsonDecode(results[6].body!)['clients'], - 'status': jsonDecode(results[1].body!), - 'filtering': jsonDecode(results[2].body!), - 'safeSearch': jsonDecode(results[3].body!), - 'safeBrowsingEnabled': jsonDecode(results[4].body!), - 'parentalControlEnabled': jsonDecode(results[5].body!), - }; - return ApiResponse( - successful: true, - content: ServerStatus.fromJson(mappedData) - ); + try { + final Map mappedData = { + 'stats': jsonDecode(results[0].body!), + 'clients': jsonDecode(results[6].body!)['clients'], + 'status': jsonDecode(results[1].body!), + 'filtering': jsonDecode(results[2].body!), + 'safeSearch': jsonDecode(results[3].body!), + 'safeBrowsingEnabled': jsonDecode(results[4].body!), + 'parentalControlEnabled': jsonDecode(results[5].body!), + }; + return ApiResponse( + successful: true, + content: ServerStatus.fromJson(mappedData) + ); + } on FormatException { + return const ApiResponse(successful: false); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } } else { return const ApiResponse(successful: false); From d43f7596626c3866c3f5b76af1a5f447f240efd4 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 24 Nov 2023 00:30:57 +0100 Subject: [PATCH 112/177] Bug fix --- lib/classes/http_client.dart | 32 ++++++++++++++++++++++------- lib/providers/servers_provider.dart | 2 +- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/lib/classes/http_client.dart b/lib/classes/http_client.dart index f14b839..c641b48 100644 --- a/lib/classes/http_client.dart +++ b/lib/classes/http_client.dart @@ -6,7 +6,7 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:adguard_home_manager/models/server.dart'; -enum ExceptionType { socket, timeout, handshake, unknown } +enum ExceptionType { socket, timeout, handshake, http, unknown } class HttpResponse { final bool successful; @@ -73,8 +73,14 @@ class HttpRequestClient { statusCode: null, exception: ExceptionType.handshake ); - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } on HttpException { + return const HttpResponse( + successful: false, + body: null, + statusCode: null, + exception: ExceptionType.http + ); + } catch (e) { return const HttpResponse( successful: false, body: null, @@ -123,6 +129,13 @@ class HttpRequestClient { 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, @@ -130,8 +143,7 @@ class HttpRequestClient { statusCode: null, exception: ExceptionType.handshake ); - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } catch (e) { return const HttpResponse( successful: false, body: null, @@ -180,6 +192,13 @@ class HttpRequestClient { 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, @@ -187,8 +206,7 @@ class HttpRequestClient { statusCode: null, exception: ExceptionType.handshake ); - } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + } catch (e) { return const HttpResponse( successful: false, body: null, diff --git a/lib/providers/servers_provider.dart b/lib/providers/servers_provider.dart index 2cc634a..55d6363 100644 --- a/lib/providers/servers_provider.dart +++ b/lib/providers/servers_provider.dart @@ -265,7 +265,7 @@ class ServersProvider with ChangeNotifier { if (data.currentVersion == data.newVersion) { final gitHubResult = await ExternalRequests.getReleaseData(releaseTag: data.newVersion ?? data.currentVersion); if (gitHubResult.successful == true) { - data.changelog = gitHubResult.content; + data.changelog = (gitHubResult.content as GitHubRelease).body; } setUpdateAvailableData(data); timer.cancel(); From 019fed0f6b438d2ed7ba137408399e4d06915aeb Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 24 Nov 2023 01:26:44 +0100 Subject: [PATCH 113/177] Bug fix --- lib/models/encryption.dart | 118 ++++++++++++++++++ .../settings/encryption/encryption.dart | 33 +++-- lib/services/api_client.dart | 4 +- 3 files changed, 137 insertions(+), 18 deletions(-) diff --git a/lib/models/encryption.dart b/lib/models/encryption.dart index 8f8a3a0..8a384c8 100644 --- a/lib/models/encryption.dart +++ b/lib/models/encryption.dart @@ -122,3 +122,121 @@ class EncryptionData { "private_key_saved": privateKeySaved, }; } + + + +EncyptionValidation encyptionValidationFromJson(String str) => EncyptionValidation.fromJson(json.decode(str)); + +String encyptionValidationToJson(EncyptionValidation data) => json.encode(data.toJson()); + +class EncyptionValidation { + final String? subject; + final String? issuer; + final String? keyType; + final DateTime? notBefore; + final DateTime? notAfter; + final String? warningValidation; + final List? 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; + + EncyptionValidation({ + 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 EncyptionValidation.fromJson(Map json) => EncyptionValidation( + 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.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 toJson() => { + "subject": subject, + "issuer": issuer, + "key_type": keyType, + "not_before": notBefore?.toIso8601String(), + "not_after": notAfter?.toIso8601String(), + "warning_validation": warningValidation, + "dns_names": dnsNames == null ? [] : List.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, + }; +} diff --git a/lib/screens/settings/encryption/encryption.dart b/lib/screens/settings/encryption/encryption.dart index 63d9620..abf1735 100644 --- a/lib/screens/settings/encryption/encryption.dart +++ b/lib/screens/settings/encryption/encryption.dart @@ -138,23 +138,22 @@ class _EncryptionSettingsState extends State { } ); - if (mounted) { - if (result.successful == true) { - setState(() { - if (result.content['warning_validation'] != null && result.content['warning_validation'] != '') { - certKeyValidApi = 2; - validDataError = result.content['warning_validation']; - } - else { - certKeyValidApi = 1; - validDataError = null; - } - certKeyValid = result.content; - }); - } - else { - setState(() => certKeyValidApi = 2); - } + if (!mounted) return; + if (result.successful == true) { + final data = result.content as EncyptionValidation; + setState(() { + if (data.warningValidation != null && data.warningValidation != '') { + certKeyValidApi = 2; + validDataError = data.warningValidation; + } + else { + certKeyValidApi = 1; + validDataError = null; + } + certKeyValid = result.content; + }); + } + else { } } diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index d99ab7c..ba56a9c 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -698,7 +698,9 @@ class ApiClientV2 { try { return ApiResponse( successful: result.successful, - content: result.body != null ? jsonDecode(result.body!) : null + content: result.body != null + ? EncyptionValidation.fromJson(jsonDecode(result.body!)) + : null ); } catch (e, stackTrace) { Sentry.captureException(e, stackTrace: stackTrace); From 22a957a12007458254e84c35812a478185f6719e Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 24 Nov 2023 01:28:33 +0100 Subject: [PATCH 114/177] Removed unused import --- lib/classes/http_client.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/classes/http_client.dart b/lib/classes/http_client.dart index c641b48..d64e1d1 100644 --- a/lib/classes/http_client.dart +++ b/lib/classes/http_client.dart @@ -2,8 +2,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:sentry_flutter/sentry_flutter.dart'; - import 'package:adguard_home_manager/models/server.dart'; enum ExceptionType { socket, timeout, handshake, http, unknown } From 1c858c0491effec8a6269c32b2c4a848849a8f20 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 24 Nov 2023 01:29:10 +0100 Subject: [PATCH 115/177] Updated app version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 9ade92d..183b144 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.11.1+106 +version: 2.11.2+107 environment: sdk: '>=2.18.1 <3.0.0' From eb866e1ebe2afd8f562d2a3960569bb31b409f99 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 24 Nov 2023 01:58:38 +0100 Subject: [PATCH 116/177] Fixed encryption settings --- .../settings/encryption/encryption.dart | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/screens/settings/encryption/encryption.dart b/lib/screens/settings/encryption/encryption.dart index abf1735..a618dbe 100644 --- a/lib/screens/settings/encryption/encryption.dart +++ b/lib/screens/settings/encryption/encryption.dart @@ -72,7 +72,7 @@ class _EncryptionSettingsState extends State { String? validDataError; int certKeyValidApi = 0; - Map? certKeyValid; + EncyptionValidation? certKeyValid; bool formEdited = false; @@ -150,7 +150,7 @@ class _EncryptionSettingsState extends State { certKeyValidApi = 1; validDataError = null; } - certKeyValid = result.content; + certKeyValid = data; }); } else { @@ -475,40 +475,40 @@ class _EncryptionSettingsState extends State { ), if (certKeyValid != null && (certificateContentController.text != '' || certificatePathController.text != '')) ...[ const SizedBox(height: 20), - if (certKeyValid!['valid_chain'] != null) ...[ + if (certKeyValid!.validChain != null) ...[ Status( - valid: certKeyValid!['valid_chain'], - label: certKeyValid!['valid_chain'] == true + valid: certKeyValid!.validChain ?? false, + label: certKeyValid!.validChain == true ? AppLocalizations.of(context)!.validCertificateChain : AppLocalizations.of(context)!.invalidCertificateChain, ), const SizedBox(height: 10), ], - if (certKeyValid!['subject'] != null) ...[ + if (certKeyValid!.subject != null) ...[ Status( valid: true, - label: "${AppLocalizations.of(context)!.subject}: ${certKeyValid!['subject']}" + label: "${AppLocalizations.of(context)!.subject}: ${certKeyValid?.subject}" ), const SizedBox(height: 10), ], - if (certKeyValid!['issuer'] != null) ...[ + if (certKeyValid!.issuer != null) ...[ Status( valid: true, - label: "${AppLocalizations.of(context)!.issuer}: ${certKeyValid!['issuer']}" + label: "${AppLocalizations.of(context)!.issuer}: ${certKeyValid?.issuer}" ), const SizedBox(height: 10), ], - if (certKeyValid!['not_after'] != null) ...[ + if (certKeyValid!.notAfter != null) ...[ Status( valid: true, - label: "${AppLocalizations.of(context)!.expirationDate}: ${certKeyValid!['not_after']}" + label: "${AppLocalizations.of(context)!.expirationDate}: ${certKeyValid?.notAfter}" ), const SizedBox(height: 10), ], - if (certKeyValid!['dns_names'] != null) ...[ + if (certKeyValid!.dnsNames != null) ...[ Status( valid: true, - label: "${AppLocalizations.of(context)!.hostNames}: ${certKeyValid!['dns_names'].join(', ')}" + label: "${AppLocalizations.of(context)!.hostNames}: ${certKeyValid?.dnsNames?.join(', ')}" ), const SizedBox(height: 10), ], @@ -586,26 +586,26 @@ class _EncryptionSettingsState extends State { ), const SizedBox(height: 20), if (certKeyValid != null && (privateKeyPathController.text != '' || pastePrivateKeyController.text != '' || usePreviouslySavedKey == true)) ...[ - if (certKeyValid!['valid_key'] != null) ...[ + if (certKeyValid!.validKey != null) ...[ Status( - valid: certKeyValid!['valid_key'], - label: certKeyValid!['valid_key'] == true + valid: certKeyValid!.validKey ?? false, + label: certKeyValid!.validKey == true ? AppLocalizations.of(context)!.validPrivateKey : AppLocalizations.of(context)!.invalidPrivateKey, ), const SizedBox(height: 10) ], - if (certKeyValid!['valid_pair'] != null && certKeyValid!['valid_pair'] == false) ...[ + if (certKeyValid!.validPair != null && certKeyValid!.validPair == false) ...[ Status( valid: false, label: AppLocalizations.of(context)!.keysNotMatch, ), const SizedBox(height: 10) ], - if (certKeyValid!['key_type'] != null) ...[ + if (certKeyValid!.keyType != null) ...[ Status( valid: true, - label: "${AppLocalizations.of(context)!.keyType}: ${certKeyValid!['key_type']}" + label: "${AppLocalizations.of(context)!.keyType}: ${certKeyValid!.keyType}" ), const SizedBox(height: 10), ], From 002fca4e3f8f79f137e441838f7d5880abbbd9c6 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 24 Nov 2023 01:59:06 +0100 Subject: [PATCH 117/177] Updated app version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 183b144..c52eb16 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.11.2+107 +version: 2.11.3+108 environment: sdk: '>=2.18.1 <3.0.0' From 8dc9539a3a8a6f468e6b3fd57bbe93edd504c549 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 24 Nov 2023 20:44:56 +0100 Subject: [PATCH 118/177] Bug fixes --- lib/models/encryption.dart | 19 ++++++--- lib/screens/filters/filters_list.dart | 2 +- .../settings/encryption/encryption.dart | 42 ++++++++++++------- lib/services/api_client.dart | 15 +++++-- 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/lib/models/encryption.dart b/lib/models/encryption.dart index 8a384c8..1315868 100644 --- a/lib/models/encryption.dart +++ b/lib/models/encryption.dart @@ -124,12 +124,19 @@ class EncryptionData { } +class EncryptionValidationResult { + final bool isObject; + final EncryptionValidation? encryptionValidation; + final String? message; -EncyptionValidation encyptionValidationFromJson(String str) => EncyptionValidation.fromJson(json.decode(str)); + const EncryptionValidationResult({ + required this.isObject, + this.encryptionValidation, + this.message + }); +} -String encyptionValidationToJson(EncyptionValidation data) => json.encode(data.toJson()); - -class EncyptionValidation { +class EncryptionValidation { final String? subject; final String? issuer; final String? keyType; @@ -156,7 +163,7 @@ class EncyptionValidation { final String? privateKeyPath; final bool? privateKeySaved; - EncyptionValidation({ + EncryptionValidation({ this.subject, this.issuer, this.keyType, @@ -184,7 +191,7 @@ class EncyptionValidation { this.privateKeySaved, }); - factory EncyptionValidation.fromJson(Map json) => EncyptionValidation( + factory EncryptionValidation.fromJson(Map json) => EncryptionValidation( subject: json["subject"], issuer: json["issuer"], keyType: json["key_type"], diff --git a/lib/screens/filters/filters_list.dart b/lib/screens/filters/filters_list.dart index 5c93fb0..f4cca03 100644 --- a/lib/screens/filters/filters_list.dart +++ b/lib/screens/filters/filters_list.dart @@ -172,7 +172,7 @@ class _FiltersListState extends State { 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, diff --git a/lib/screens/settings/encryption/encryption.dart b/lib/screens/settings/encryption/encryption.dart index a618dbe..f47a63d 100644 --- a/lib/screens/settings/encryption/encryption.dart +++ b/lib/screens/settings/encryption/encryption.dart @@ -72,7 +72,8 @@ class _EncryptionSettingsState extends State { String? validDataError; int certKeyValidApi = 0; - EncyptionValidation? certKeyValid; + EncryptionValidation? certKeyValid; + String? encryptionResultMessage; bool formEdited = false; @@ -140,20 +141,27 @@ class _EncryptionSettingsState extends State { if (!mounted) return; if (result.successful == true) { - final data = result.content as EncyptionValidation; - setState(() { - if (data.warningValidation != null && data.warningValidation != '') { + final data = result.content as EncryptionValidationResult; + if (data.isObject == true) { + final object = data.encryptionValidation!; + setState(() { + if (object.warningValidation != null && object.warningValidation != '') { + certKeyValidApi = 2; + validDataError = object.warningValidation; + } + else { + certKeyValidApi = 1; + validDataError = null; + } + certKeyValid = object; + }); + } + else { + setState(() { + encryptionResultMessage = data.message; certKeyValidApi = 2; - validDataError = data.warningValidation; - } - else { - certKeyValidApi = 1; - validDataError = null; - } - certKeyValid = data; - }); - } - else { + }); + } } } @@ -253,11 +261,13 @@ class _EncryptionSettingsState extends State { centerTitle: false, actions: [ IconButton( - onPressed: certKeyValidApi == 2 && validDataError != null + onPressed: certKeyValidApi == 2 && (validDataError != null || encryptionResultMessage != null) ? () => { showDialog( context: context, - builder: (context) => EncryptionErrorModal(error: validDataError!) + builder: (context) => EncryptionErrorModal( + error: validDataError ?? encryptionResultMessage ?? AppLocalizations.of(context)!.unknownError + ) ) } : null, icon: generateStatus(context, appConfigProvider, localValidationValid, certKeyValidApi, formEdited), diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index ba56a9c..66ac615 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -698,9 +698,18 @@ class ApiClientV2 { try { return ApiResponse( successful: result.successful, - content: result.body != null - ? EncyptionValidation.fromJson(jsonDecode(result.body!)) - : null + content: result.body != null ? EncryptionValidationResult( + isObject: false, + encryptionValidation: EncryptionValidation.fromJson(jsonDecode(result.body!)) + ) : null + ); + } on FormatException { + return ApiResponse( + successful: result.successful, + content: result.body != null ? EncryptionValidationResult( + isObject: false, + message: result.body + ) : null ); } catch (e, stackTrace) { Sentry.captureException(e, stackTrace: stackTrace); From 28229311c0b575a1833d374c37e3cc1da5157a5b Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 25 Nov 2023 19:02:36 +0100 Subject: [PATCH 119/177] Updated libraries --- pubspec.lock | 34 +++++++++++++++++++++------------- pubspec.yaml | 10 +++++----- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 0582275..274cfb3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.8" + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "8bf17a8ff6ea17499e40a2d2542c2f481cd7615760c6d34065cb22bfd22e6880" + url: "https://pub.dev" + source: hosted + version: "2.0.2" archive: dependency: transitive description: @@ -133,10 +141,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "7035152271ff67b072a211152846e9f1259cf1be41e34cd3e0b5463d2d6b8419" + sha256: "0042cb3b2a76413ea5f8a2b40cec2a33e01d0c937e91f0f7c211fde4f7739ba6" url: "https://pub.dev" source: hosted - version: "9.1.0" + version: "9.1.1" device_info_plus_platform_interface: dependency: transitive description: @@ -197,10 +205,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "6b9eb2b3017241d05c482c01f668dd05cc909ec9a0114fdd49acd958ff2432fa" + sha256: "5a74434cc83bf64346efb562f1a06eefaf1bcb530dc3d96a104f631a1eff8d79" url: "https://pub.dev" source: hosted - version: "0.64.0" + version: "0.65.0" flutter: dependency: "direct main" description: flutter @@ -263,10 +271,10 @@ packages: dependency: "direct dev" description: name: flutter_native_splash - sha256: d93394f22f73e810bda59e11ebe83329c5511d6460b6b7509c4e1f3c92d6d625 + sha256: c4d899312b36e7454bedfd0a4740275837b99e532d81c8477579d8183db1de6c url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.3.6" flutter_reorderable_list: dependency: "direct main" description: @@ -426,10 +434,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" + sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79" url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "5.0.1" package_info_plus_platform_interface: dependency: transitive description: @@ -514,18 +522,18 @@ packages: dependency: transitive description: name: sentry - sha256: e8040183ea1a0323999bce69786ed8429b1b89fbe5815a504d5bb7493a6464cc + sha256: e7ded42974bac5f69e4ca4ddc57d30499dd79381838f24b7e8fd9aa4139e7b79 url: "https://pub.dev" source: hosted - version: "7.13.1" + version: "7.13.2" sentry_flutter: dependency: "direct main" description: name: sentry_flutter - sha256: "843e317e605e5860e30ae431e9b8724c54f1a8567c9ad495e04595926bf22376" + sha256: d6f55ec7a1f681784165021f749007712a72ff57eadf91e963331b6ae326f089 url: "https://pub.dev" source: hosted - version: "7.13.1" + version: "7.13.2" sky_engine: dependency: transitive description: flutter @@ -813,5 +821,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" + dart: ">=3.2.0 <4.0.0" flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index c52eb16..d4f6f8e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,14 +41,14 @@ dependencies: intl: any provider: ^6.1.1 sqflite: ^2.3.0 - package_info_plus: ^4.2.0 + package_info_plus: ^5.0.1 flutter_displaymode: ^0.6.0 dynamic_color: ^1.6.8 animations: ^2.0.8 - device_info_plus: ^9.1.0 + device_info_plus: ^9.1.1 uuid: ^4.2.1 expandable: ^5.0.1 - fl_chart: ^0.64.0 + fl_chart: ^0.65.0 flutter_web_browser: ^0.17.1 flutter_svg: ^2.0.9 percent_indicator: ^4.2.3 @@ -70,7 +70,7 @@ dependencies: url_launcher: ^6.1.11 contextmenu: ^3.0.0 async: ^2.11.0 - sentry_flutter: ^7.13.1 + sentry_flutter: ^7.13.2 flutter_dotenv: ^5.1.0 flutter_reorderable_list: ^1.3.1 pie_chart: ^5.4.0 @@ -88,7 +88,7 @@ dev_dependencies: # rules and activating additional ones. flutter_lints: ^3.0.1 flutter_launcher_icons: ^0.13.1 - flutter_native_splash: ^2.2.10+1 + flutter_native_splash: ^2.3.6 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From 07bd3dcb9ad5173f96be019cda8a1f78b8073635 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 25 Nov 2023 19:03:26 +0100 Subject: [PATCH 120/177] Added top upstreams and average processing time --- lib/config/home_top_items_default_order.dart | 4 +- lib/constants/enums.dart | 2 +- lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/models/dns_statistics.dart | 8 + lib/providers/app_config_provider.dart | 13 +- lib/providers/status_provider.dart | 2 +- lib/screens/home/home.dart | 47 +++- lib/screens/home/top_items/row_item.dart | 21 +- .../top_items/top_item_expansion_panel.dart | 193 ++++++++++++++++ lib/screens/home/top_items/top_items.dart | 208 ++++++++---------- .../reorderable_top_items_home.dart | 53 +++-- lib/screens/top_items/top_items.dart | 29 ++- lib/screens/top_items/top_items_modal.dart | 29 ++- lib/widgets/domain_options.dart | 12 +- 15 files changed, 441 insertions(+), 188 deletions(-) create mode 100644 lib/screens/home/top_items/top_item_expansion_panel.dart diff --git a/lib/config/home_top_items_default_order.dart b/lib/config/home_top_items_default_order.dart index 5220987..a5a23f0 100644 --- a/lib/config/home_top_items_default_order.dart +++ b/lib/config/home_top_items_default_order.dart @@ -5,7 +5,9 @@ import 'package:adguard_home_manager/constants/enums.dart'; final List homeTopItemsDefaultOrder = [ HomeTopItems.queriedDomains, HomeTopItems.blockedDomains, - HomeTopItems.recurrentClients + HomeTopItems.recurrentClients, + HomeTopItems.topUpstreams, + HomeTopItems.avgUpstreamResponseTime ]; final String homeTopItemsDefaultOrderString = jsonEncode( diff --git a/lib/constants/enums.dart b/lib/constants/enums.dart index fed648e..f54e665 100644 --- a/lib/constants/enums.dart +++ b/lib/constants/enums.dart @@ -1,2 +1,2 @@ enum LoadStatus { loading, loaded, error } -enum HomeTopItems { queriedDomains, blockedDomains, recurrentClients } \ No newline at end of file +enum HomeTopItems { queriedDomains, blockedDomains, recurrentClients, topUpstreams, avgUpstreamResponseTime } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7c126ba..b9ba629 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -686,5 +686,7 @@ "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}" + "minimumRequiredVersion": "Minimum required version: {version}", + "topUpstreams": "Top upstreams", + "averageUpstreamResponseTime": "Average upstream response time" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 9033460..0e1566c 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -686,5 +686,7 @@ "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}" + "minimumRequiredVersion": "Versión mínima requerida: {version}", + "topUpstreams": "DNS de subida más frecuentes", + "averageUpstreamResponseTime": "Tiempo promedio de respuesta upstream" } \ No newline at end of file diff --git a/lib/models/dns_statistics.dart b/lib/models/dns_statistics.dart index 45993bd..f279886 100644 --- a/lib/models/dns_statistics.dart +++ b/lib/models/dns_statistics.dart @@ -9,6 +9,8 @@ class DnsStatistics { final List> topQueriedDomains; final List> topClients; final List> topBlockedDomains; + final List>? topUpstreamResponses; + final List>? topUpstreamsAvgTime; final List dnsQueries; final List blockedFiltering; final List 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>.from(json["top_queried_domains"].map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))), topClients: List>.from(json["top_clients"].map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))), topBlockedDomains: List>.from(json["top_blocked_domains"].map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))), + topUpstreamResponses: json["top_upstreams_responses"] != null ? List>.from(json["top_upstreams_responses"].map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))) : null, + topUpstreamsAvgTime: json["top_upstreams_avg_time"] != null ? List>.from(json["top_upstreams_avg_time"].map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))) : null, dnsQueries: List.from(json["dns_queries"].map((x) => x)), blockedFiltering: List.from(json["blocked_filtering"].map((x) => x)), replacedSafebrowsing: List.from(json["replaced_safebrowsing"].map((x) => x)), @@ -59,6 +65,8 @@ class DnsStatistics { "top_queried_domains": List.from(topQueriedDomains.map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))), "top_clients": List.from(topClients.map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))), "top_blocked_domains": List.from(topBlockedDomains.map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))), + "top_upstreams_responses": topUpstreamResponses != null ? List.from(topUpstreamResponses!.map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))) : null, + "top_upstreams_avg_time": topUpstreamsAvgTime != null ? List.from(topUpstreamsAvgTime!.map((x) => Map.from(x).map((k, v) => MapEntry(k, v)))) : null, "dns_queries": List.from(dnsQueries.map((x) => x)), "blocked_filtering": List.from(blockedFiltering.map((x) => x)), "replaced_safebrowsing": List.from(replacedSafebrowsing.map((x) => x)), diff --git a/lib/providers/app_config_provider.dart b/lib/providers/app_config_provider.dart index 16e855f..d44b5af 100644 --- a/lib/providers/app_config_provider.dart +++ b/lib/providers/app_config_provider.dart @@ -449,7 +449,7 @@ class AppConfigProvider with ChangeNotifier { _showTopItemsChart = dbData['showTopItemsChart']; if (dbData['homeTopItemsOrder'] != null) { try { - _homeTopItemsOrder = List.from( + final itemsOrder = List.from( List.from(jsonDecode(dbData['homeTopItemsOrder'])).map((e) { switch (e) { case 'queriedDomains': @@ -461,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; diff --git a/lib/providers/status_provider.dart b/lib/providers/status_provider.dart index 0c519f8..527749f 100644 --- a/lib/providers/status_provider.dart +++ b/lib/providers/status_provider.dart @@ -237,7 +237,7 @@ class StatusProvider with ChangeNotifier { } Future getServerStatus({ - bool? withLoadingIndicator, + bool? withLoadingIndicator = true, bool? overrideCheckServerVersion }) async { if (withLoadingIndicator == true) { diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index 343d065..ec48e54 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -32,8 +32,11 @@ class _HomeState extends State { late bool isVisible; @override - initState(){ - Provider.of(context, listen: false).getServerStatus(); + initState() { + final statusProvider = Provider.of(context, listen: false); + statusProvider.getServerStatus( + withLoadingIndicator: statusProvider.serverStatus != null ? false : true + ); super.initState(); @@ -239,9 +242,9 @@ class TopItemsLists extends StatelessWidget { final List order; const TopItemsLists({ - Key? key, + super.key, required this.order, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -266,8 +269,7 @@ class TopItemsLists extends StatelessWidget { children: [ TopItems( label: AppLocalizations.of(context)!.topQueriedDomains, - data: statusProvider.serverStatus!.stats.topQueriedDomains, - type: 'topQueriedDomains', + type: HomeTopItems.queriedDomains, ), if (item.key < order.length - 1) ...bottom ], @@ -278,8 +280,7 @@ class TopItemsLists extends StatelessWidget { children: [ TopItems( label: AppLocalizations.of(context)!.topBlockedDomains, - data: statusProvider.serverStatus!.stats.topBlockedDomains, - type: 'topBlockedDomains', + type: HomeTopItems.blockedDomains, ), if (item.key < order.length - 1) ...bottom ], @@ -290,13 +291,37 @@ class TopItemsLists extends StatelessWidget { children: [ TopItems( label: AppLocalizations.of(context)!.topClients, - data: statusProvider.serverStatus!.stats.topClients, - type: 'topClients', - clients: true, + type: HomeTopItems.recurrentClients, ), if (item.key < order.length - 1) ...bottom ], ); + + case HomeTopItems.topUpstreams: + return statusProvider.serverStatus!.stats.topUpstreamResponses != null + ? Column( + children: [ + TopItems( + label: AppLocalizations.of(context)!.topUpstreams, + type: HomeTopItems.topUpstreams, + ), + if (item.key < order.length - 1) ...bottom + ], + ) + : const SizedBox(); + + case HomeTopItems.avgUpstreamResponseTime: + return statusProvider.serverStatus!.stats.topUpstreamsAvgTime != null + ? Column( + children: [ + TopItems( + label: AppLocalizations.of(context)!.averageUpstreamResponseTime, + type: HomeTopItems.avgUpstreamResponseTime, + ), + if (item.key < order.length - 1) ...bottom + ], + ) + : const SizedBox(); default: return const SizedBox(); diff --git a/lib/screens/home/top_items/row_item.dart b/lib/screens/home/top_items/row_item.dart index dbbd6d4..4e3c184 100644 --- a/lib/screens/home/top_items/row_item.dart +++ b/lib/screens/home/top_items/row_item.dart @@ -4,28 +4,31 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/domain_options.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/models/applied_filters.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; class RowItem extends StatefulWidget { - final String type; + final HomeTopItems type; final Color chartColor; final String domain; final String number; final bool clients; final bool showColor; + final String? unit; const RowItem({ - Key? key, + super.key, required this.type, required this.chartColor, required this.domain, required this.number, required this.clients, required this.showColor, - }) : super(key: key); + this.unit, + }); @override State createState() => _RowItemState(); @@ -93,10 +96,10 @@ class _RowItemState extends State with TickerProviderStateMixin { color: Colors.transparent, child: DomainOptions( item: widget.domain, - isClient: widget.type == 'topClients', - isBlocked: widget.type == 'topBlockedDomains', + isDomain: widget.type == HomeTopItems.queriedDomains || widget.type == HomeTopItems.blockedDomains, + isBlocked: widget.type == HomeTopItems.blockedDomains, onTap: () { - if (widget.type == 'topQueriedDomains' || widget.type == 'topBlockedDomains') { + if (widget.type == HomeTopItems.queriedDomains || widget.type == HomeTopItems.blockedDomains) { logsProvider.setSearchText(widget.domain); logsProvider.setSelectedClients(null); logsProvider.setAppliedFilters( @@ -108,7 +111,7 @@ class _RowItemState extends State with TickerProviderStateMixin { ); appConfigProvider.setSelectedScreen(2); } - else if (widget.type == 'topClients') { + else if (widget.type == HomeTopItems.recurrentClients) { logsProvider.setSearchText(null); logsProvider.setSelectedClients([widget.domain]); logsProvider.setAppliedFilters( @@ -195,10 +198,10 @@ class OthersRowItem extends StatefulWidget { final bool showColor; const OthersRowItem({ - Key? key, + super.key, required this.items, required this.showColor, - }) : super(key: key); + }); @override State createState() => _OthersRowItemState(); diff --git a/lib/screens/home/top_items/top_item_expansion_panel.dart b/lib/screens/home/top_items/top_item_expansion_panel.dart new file mode 100644 index 0000000..eb62d01 --- /dev/null +++ b/lib/screens/home/top_items/top_item_expansion_panel.dart @@ -0,0 +1,193 @@ +import 'package:flutter/material.dart'; + +import 'package:adguard_home_manager/widgets/custom_pie_chart.dart'; +import 'package:adguard_home_manager/screens/home/top_items/row_item.dart'; + +import 'package:adguard_home_manager/constants/enums.dart'; + +class TopItemExpansionPanel extends StatefulWidget { + final HomeTopItems type; + final String label; + final List> data; + final Map chartData; + final bool withChart; + + const TopItemExpansionPanel({ + super.key, + required this.type, + required this.label, + required this.data, + required this.chartData, + required this.withChart + }); + + @override + State createState() => _TopItemExpansionPanelState(); +} + +class _TopItemExpansionPanelState extends State { + bool _showChart = true; + + final colors = [ + Colors.red, + Colors.green, + Colors.blue, + Colors.orange, + Colors.teal, + Colors.grey + ]; + + @override + Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + + if (widget.withChart == true) { + return Column( + children: [ + ExpansionPanelList( + expandedHeaderPadding: const EdgeInsets.all(0), + elevation: 0, + expansionCallback: (_, isExpanded) => setState(() => _showChart = isExpanded), + animationDuration: const Duration(milliseconds: 250), + children: [ + ExpansionPanel( + headerBuilder: (context, isExpanded) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Row( + mainAxisAlignment: width <= 700 + ? MainAxisAlignment.spaceBetween + : MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + widget.label, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ), + ], + ), + ), + body: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + children: [ + SizedBox( + height: 150, + child: CustomPieChart( + data: widget.chartData, + colors: colors + ) + ), + const SizedBox(height: 16), + ], + ), + ), + isExpanded: _showChart + ), + ], + ), + Padding( + padding: const EdgeInsets.only(top: 8), + child: _ItemsList( + colors: colors, + data: widget.data, + clients: widget.type == HomeTopItems.recurrentClients, + type: widget.type, + showChart: _showChart, + unit: widget.type == HomeTopItems.avgUpstreamResponseTime ? 'ms' : null, + ), + ), + if (widget.type != HomeTopItems.avgUpstreamResponseTime) OthersRowItem( + items: widget.data, + showColor: _showChart, + ), + const SizedBox(height: 16), + ], + ); + } + else { + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 18), + child: Row( + mainAxisAlignment: width <= 700 + ? MainAxisAlignment.spaceBetween + : MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + widget.label, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(top: 16), + child: _ItemsList( + colors: colors, + data: widget.data, + clients: widget.type == HomeTopItems.recurrentClients, + type: widget.type, + showChart: false, + unit: widget.type == HomeTopItems.avgUpstreamResponseTime ? 'ms' : null, + ), + ), + if (widget.type != HomeTopItems.avgUpstreamResponseTime) OthersRowItem( + items: widget.data, + showColor: false, + ), + const SizedBox(height: 16), + ], + ); + } + } +} + +class _ItemsList extends StatelessWidget { + final List colors; + final List> data; + final bool? clients; + final HomeTopItems type; + final bool showChart; + final String? unit; + + const _ItemsList({ + required this.colors, + required this.data, + required this.clients, + required this.type, + required this.showChart, + this.unit, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: data.sublist( + 0, data.length > 5 ? 5 : data.length + ).asMap().entries.map((e) => RowItem( + clients: clients ?? false, + domain: e.value.keys.toList()[0], + number: e.value.values.toList()[0].runtimeType == double + ? "${e.value.values.toList()[0].toStringAsFixed(2)}${unit != null ? ' $unit' : ''}" + : "${e.value.values.toList()[0].toString()}${unit != null ? ' $unit' : ''}", + type: type, + chartColor: colors[e.key], + showColor: showChart, + )).toList() + ); + } +} \ No newline at end of file diff --git a/lib/screens/home/top_items/top_items.dart b/lib/screens/home/top_items/top_items.dart index 5fd6435..6276d74 100644 --- a/lib/screens/home/top_items/top_items.dart +++ b/lib/screens/home/top_items/top_items.dart @@ -7,26 +7,24 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/home/top_items/row_item.dart'; -import 'package:adguard_home_manager/widgets/custom_pie_chart.dart'; +import 'package:adguard_home_manager/screens/home/top_items/top_item_expansion_panel.dart'; import 'package:adguard_home_manager/screens/top_items/top_items_modal.dart'; import 'package:adguard_home_manager/screens/top_items/top_items.dart'; +import 'package:adguard_home_manager/widgets/custom_pie_chart.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class TopItems extends StatefulWidget { - final String type; + final HomeTopItems type; final String label; - final List> data; - final bool? clients; const TopItems({ - Key? key, + super.key, required this.type, required this.label, - required this.data, - this.clients - }) : super(key: key); + }); @override State createState() => _TopItemsState(); @@ -58,31 +56,41 @@ class _TopItemsState extends State { List> generateData() { switch (widget.type) { - case 'topQueriedDomains': + case HomeTopItems.queriedDomains: return statusProvider.serverStatus!.stats.topQueriedDomains; - case 'topBlockedDomains': + case HomeTopItems.blockedDomains: return statusProvider.serverStatus!.stats.topBlockedDomains; - case 'topClients': + case HomeTopItems.recurrentClients: return statusProvider.serverStatus!.stats.topClients; + case HomeTopItems.topUpstreams: + return statusProvider.serverStatus!.stats.topUpstreamResponses ?? []; + + case HomeTopItems.avgUpstreamResponseTime: + return statusProvider.serverStatus!.stats.topUpstreamsAvgTime ?? []; + default: return []; } } + final data = generateData(); + + final withChart = widget.type != HomeTopItems.avgUpstreamResponseTime; + Map chartData() { Map values = {}; - widget.data.sublist(0, widget.data.length > 5 ? 5 : widget.data.length).forEach((element) { + data.sublist(0, data.length > 5 ? 5 : data.length).forEach((element) { values = { ...values, element.keys.first: element.values.first.toDouble() }; }); - if (widget.data.length > 5) { + if (data.length > 5) { final int rest = List.from( - widget.data.sublist(5, widget.data.length).map((e) => e.values.first.toInt()) + data.sublist(5, data.length).map((e) => e.values.first.toInt()) ).reduce((a, b) => a + b); values = { ...values, @@ -109,108 +117,71 @@ class _TopItemsState extends State { return SizedBox( child: Column( children: [ - if (widget.data.isEmpty) noItems, - if (widget.data.isNotEmpty && width > 700) Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - flex: 1, - child: ConstrainedBox( - constraints: const BoxConstraints( - maxHeight: 250 - ), - child: Padding( - padding: const EdgeInsets.all(16), - child: CustomPieChart( - data: chartData(), - colors: colors - ) - ), - ) - ), - Expanded( - flex: 2, - child: Column( - children: [ - ItemsList( - colors: colors, - data: widget.data, - clients: widget.clients, - type: widget.type, - showChart: _showChart - ), - OthersRowItem( - items: widget.data, - showColor: true, - ) - ] - ), - ) - ], - ), - if (widget.data.isNotEmpty && width <= 700) ...[ - ExpansionPanelList( - expandedHeaderPadding: const EdgeInsets.all(0), - elevation: 0, - expansionCallback: (_, isExpanded) => setState(() => _showChart = isExpanded), - animationDuration: const Duration(milliseconds: 250), + if (data.isEmpty) noItems, + if (data.isNotEmpty && width > 700) Padding( + padding: EdgeInsets.only(bottom: withChart == false ? 16 : 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - ExpansionPanel( - headerBuilder: (context, isExpanded) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Row( - mainAxisAlignment: width <= 700 - ? MainAxisAlignment.spaceBetween - : MainAxisAlignment.center, - children: [ - Text( + if (withChart == true) Expanded( + flex: 1, + child: ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 250 + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: CustomPieChart( + data: chartData(), + colors: colors + ) + ), + ) + ), + Expanded( + flex: 2, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + top: 8, + bottom: 16 + ), + child: Text( widget.label, - style: TextStyle( + style: const TextStyle( fontSize: 18, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface + fontWeight: FontWeight.w500 ), ), - ], - ), + ), + _ItemsList( + colors: colors, + data: data, + clients: widget.type == HomeTopItems.recurrentClients, + type: widget.type, + showChart: withChart == true ? _showChart : false, + unit: widget.type == HomeTopItems.avgUpstreamResponseTime ? 'ms' : null, + ), + if (withChart == true) OthersRowItem( + items: data, + showColor: true, + ) + ] ), - body: Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Column( - children: [ - SizedBox( - height: 150, - child: CustomPieChart( - data: chartData(), - colors: colors - ) - ), - const SizedBox(height: 16), - ], - ), - ), - isExpanded: _showChart - ), + ) ], ), - Padding( - padding: const EdgeInsets.only(top: 8), - child: ItemsList( - colors: colors, - data: widget.data, - clients: widget.clients, - type: widget.type, - showChart: _showChart - ), - ), - OthersRowItem( - items: widget.data, - showColor: _showChart, - ), - const SizedBox(height: 16), - ], + ), + if (data.isNotEmpty && width <= 700) TopItemExpansionPanel( + type: widget.type, + label: widget.label, + data: data, + chartData: chartData(), + withChart: withChart + ), - if (widget.data.length > 5) ...[ + if (data.length > 5) ...[ Padding( padding: const EdgeInsets.only(right: 20), child: Row( @@ -225,8 +196,10 @@ class _TopItemsState extends State { builder: (context) => TopItemsModal( type: widget.type, title: widget.label, - isClient: widget.clients, + isClient: widget.type == HomeTopItems.recurrentClients, data: generateData(), + withProgressBar: widget.type != HomeTopItems.avgUpstreamResponseTime, + unit: widget.type == HomeTopItems.avgUpstreamResponseTime ? 'ms' : null, ) ) } @@ -236,8 +209,10 @@ class _TopItemsState extends State { builder: (context) => TopItemsScreen( type: widget.type, title: widget.label, - isClient: widget.clients, + isClient: widget.type == HomeTopItems.recurrentClients, data: generateData(), + withProgressBar: widget.type != HomeTopItems.avgUpstreamResponseTime, + unit: widget.type == HomeTopItems.avgUpstreamResponseTime ? 'ms' : null, ) ) ) @@ -266,21 +241,22 @@ class _TopItemsState extends State { } } -class ItemsList extends StatelessWidget { +class _ItemsList extends StatelessWidget { final List colors; final List> data; final bool? clients; - final String type; + final HomeTopItems type; final bool showChart; + final String? unit; - const ItemsList({ - Key? key, + const _ItemsList({ required this.colors, required this.data, required this.clients, required this.type, required this.showChart, - }) : super(key: key); + this.unit, + }); @override Widget build(BuildContext context) { @@ -290,7 +266,9 @@ class ItemsList extends StatelessWidget { ).asMap().entries.map((e) => RowItem( clients: clients ?? false, domain: e.value.keys.toList()[0], - number: e.value.values.toList()[0].toString(), + number: e.value.values.toList()[0].runtimeType == double + ? "${e.value.values.toList()[0].toStringAsFixed(2)}${unit != null ? ' $unit' : ''}" + : "${e.value.values.toList()[0].toString()}${unit != null ? ' $unit' : ''}", type: type, chartColor: colors[e.key], showColor: showChart, diff --git a/lib/screens/settings/general_settings/reorderable_top_items_home.dart b/lib/screens/settings/general_settings/reorderable_top_items_home.dart index b649ff5..2b7bfe1 100644 --- a/lib/screens/settings/general_settings/reorderable_top_items_home.dart +++ b/lib/screens/settings/general_settings/reorderable_top_items_home.dart @@ -1,6 +1,5 @@ // ignore_for_file: use_build_context_synchronously -import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -9,15 +8,16 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; -class ItemData { +class _ItemData { final HomeTopItems title; final Key key; - const ItemData({ + const _ItemData({ required this.title, required this.key }); @@ -29,7 +29,7 @@ enum DraggingMode { } class ReorderableTopItemsHome extends StatefulWidget { - const ReorderableTopItemsHome({Key? key}) : super(key: key); + const ReorderableTopItemsHome({super.key}); @override State createState() => _ReorderableTopItemsHomeState(); @@ -38,10 +38,10 @@ class ReorderableTopItemsHome extends StatefulWidget { class _ReorderableTopItemsHomeState extends State { List homeTopItemsList = []; List persistHomeTopItemsList = []; - List renderItems = []; + List<_ItemData> renderItems = []; int _indexOfKey(Key key) { - return renderItems.indexWhere((ItemData d) => d.key == key); + return renderItems.indexWhere((_ItemData d) => d.key == key); } bool _reorderCallback(Key item, Key newPosition) { @@ -79,7 +79,7 @@ class _ReorderableTopItemsHomeState extends State { homeTopItemsList = appConfigProvider.homeTopItemsOrder; persistHomeTopItemsList = appConfigProvider.homeTopItemsOrder; renderItems = appConfigProvider.homeTopItemsOrder.asMap().entries.map( - (e) => ItemData( + (e) => _ItemData( key: ValueKey(e.key), title: e.value, ) @@ -117,16 +117,31 @@ class _ReorderableTopItemsHomeState extends State { padding: const EdgeInsets.all(16) ); + case HomeTopItems.topUpstreams: + return CustomListTile( + title: AppLocalizations.of(context)!.topUpstreams, + icon: Icons.upload_file_rounded, + padding: const EdgeInsets.all(16) + ); + + case HomeTopItems.avgUpstreamResponseTime: + return CustomListTile( + title: AppLocalizations.of(context)!.averageUpstreamResponseTime, + icon: Icons.timer_rounded, + padding: const EdgeInsets.all(16) + ); + default: return const SizedBox(); } } - Future onWillPopScope() async { + Future onWillPopScope(bool popInvoked) async { if (!listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList)) { - showDialog( + await showDialog( context: context, - builder: (dialogContext) => AlertDialog( + useRootNavigator: false, + builder: (ctx) => AlertDialog( title: Text(AppLocalizations.of(context)!.discardChanges), content: Text(AppLocalizations.of(context)!.discardChangesDescription), actions: [ @@ -135,14 +150,14 @@ class _ReorderableTopItemsHomeState extends State { children: [ TextButton( onPressed: () { - Navigator.pop(dialogContext); + Navigator.pop(context); Navigator.pop(context); }, child: Text(AppLocalizations.of(context)!.confirm) ), const SizedBox(width: 8), TextButton( - onPressed: () => Navigator.pop(dialogContext), + onPressed: () => Navigator.pop(context), child: Text(AppLocalizations.of(context)!.cancel) ), ], @@ -175,8 +190,9 @@ class _ReorderableTopItemsHomeState extends State { } } - return WillPopScope( - onWillPop: onWillPopScope, + return PopScope( + canPop: listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList), + onPopInvoked: onWillPopScope, child: Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.topItemsOrder), @@ -219,7 +235,7 @@ class _ReorderableTopItemsHomeState extends State { child: ListView.builder( itemBuilder: (context, index) => reorderable_list_library.ReorderableItem( key: renderItems[index].key, - childBuilder: (context, state) => Item( + childBuilder: (context, state) => _Item( tileWidget: tile(renderItems[index].title), isFirst: index == 0, isLast: index == renderItems.length - 1, @@ -237,19 +253,18 @@ class _ReorderableTopItemsHomeState extends State { } } -class Item extends StatelessWidget { +class _Item extends StatelessWidget { final Widget tileWidget; final bool isFirst; final bool isLast; final reorderable_list_library.ReorderableItemState state; - const Item({ - Key? key, + const _Item({ required this.tileWidget, required this.isFirst, required this.isLast, required this.state, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/screens/top_items/top_items.dart b/lib/screens/top_items/top_items.dart index 26b84d5..66ca288 100644 --- a/lib/screens/top_items/top_items.dart +++ b/lib/screens/top_items/top_items.dart @@ -10,6 +10,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/domain_options.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/models/applied_filters.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; @@ -18,18 +19,22 @@ import 'package:adguard_home_manager/functions/number_format.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class TopItemsScreen extends StatefulWidget { - final String type; + final HomeTopItems type; final String title; final bool? isClient; final List> data; + final bool withProgressBar; + final String? unit; const TopItemsScreen({ - Key? key, + super.key, required this.type, required this.title, this.isClient, required this.data, - }) : super(key: key); + required this.withProgressBar, + this.unit, + }); @override State createState() => _TopItemsScreenState(); @@ -60,9 +65,9 @@ class _TopItemsScreenState extends State { final appConfigProvider = Provider.of(context); final logsProvider = Provider.of(context); - int total = 0; + double total = 0; for (var element in data) { - total = total + int.parse(element.values.toList()[0].toString()); + total = total + double.parse(element.values.toList()[0].toString()); } return Scaffold( @@ -154,10 +159,10 @@ class _TopItemsScreenState extends State { return DomainOptions( item: screenData[index].keys.toList()[0], - isBlocked: widget.type == 'topBlockedDomains', - isClient: widget.type == 'topClients', + isBlocked: widget.type == HomeTopItems.blockedDomains, + isDomain: widget.type == HomeTopItems.queriedDomains || widget.type == HomeTopItems.blockedDomains, onTap: () { - if (widget.type == 'topQueriedDomains' || widget.type == 'topBlockedDomains') { + if (widget.type == HomeTopItems.queriedDomains || widget.type == HomeTopItems.blockedDomains) { logsProvider.setSearchText(screenData[index].keys.toList()[0]); logsProvider.setSelectedClients(null); logsProvider.setAppliedFilters( @@ -170,7 +175,7 @@ class _TopItemsScreenState extends State { appConfigProvider.setSelectedScreen(2); Navigator.pop(context); } - else if (widget.type == 'topClients') { + else if (widget.type == HomeTopItems.recurrentClients) { logsProvider.setSearchText(null); logsProvider.setSelectedClients([screenData[index].keys.toList()[0]]); logsProvider.setAppliedFilters( @@ -187,7 +192,9 @@ class _TopItemsScreenState extends State { child: CustomListTile( title: screenData[index].keys.toList()[0], trailing: Text( - screenData[index].values.toList()[0].toString(), + screenData[index].values.toList()[0].runtimeType == double + ? "${screenData[index].values.toList()[0].toStringAsFixed(2)}${widget.unit != null ? ' ${widget.unit}' : ''}" + : "${screenData[index].values.toList()[0].toString()}${widget.unit != null ? ' ${widget.unit}' : ''}", style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant ), @@ -205,7 +212,7 @@ class _TopItemsScreenState extends State { ), const SizedBox(height: 5), ], - Row( + if (widget.withProgressBar == true) Row( children: [ SizedBox( width: 50, diff --git a/lib/screens/top_items/top_items_modal.dart b/lib/screens/top_items/top_items_modal.dart index 845459a..1325c79 100644 --- a/lib/screens/top_items/top_items_modal.dart +++ b/lib/screens/top_items/top_items_modal.dart @@ -10,6 +10,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/domain_options.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/models/applied_filters.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/functions/number_format.dart'; @@ -17,18 +18,22 @@ import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class TopItemsModal extends StatefulWidget { - final String type; + final HomeTopItems type; final String title; final bool? isClient; final List> data; + final bool withProgressBar; + final String? unit; const TopItemsModal({ - Key? key, + super.key, required this.type, required this.title, this.isClient, required this.data, - }) : super(key: key); + required this.withProgressBar, + this.unit, + }); @override State createState() => _TopItemsModalState(); @@ -59,9 +64,9 @@ class _TopItemsModalState extends State { final appConfigProvider = Provider.of(context); final logsProvider = Provider.of(context); - int total = 0; + double total = 0; for (var element in data) { - total = total + int.parse(element.values.toList()[0].toString()); + total = total + double.parse(element.values.toList()[0].toString()); } return Dialog( @@ -129,11 +134,11 @@ class _TopItemsModalState extends State { } return DomainOptions( - isBlocked: widget.type == 'topBlockedDomains', - isClient: widget.type == 'topClients', + isBlocked: widget.type == HomeTopItems.blockedDomains, + isDomain: widget.type == HomeTopItems.queriedDomains || widget.type == HomeTopItems.blockedDomains, item: screenData[index].keys.toList()[0], onTap: () { - if (widget.type == 'topQueriedDomains' || widget.type == 'topBlockedDomains') { + if (widget.type == HomeTopItems.queriedDomains || widget.type == HomeTopItems.blockedDomains) { logsProvider.setSearchText(screenData[index].keys.toList()[0]); logsProvider.setSelectedClients(null); logsProvider.setAppliedFilters( @@ -146,7 +151,7 @@ class _TopItemsModalState extends State { appConfigProvider.setSelectedScreen(2); Navigator.pop(context); } - else if (widget.type == 'topClients') { + else if (widget.type == HomeTopItems.recurrentClients) { logsProvider.setSearchText(null); logsProvider.setSelectedClients([screenData[index].keys.toList()[0]]); logsProvider.setAppliedFilters( @@ -163,7 +168,9 @@ class _TopItemsModalState extends State { child: CustomListTile( title: screenData[index].keys.toList()[0], trailing: Text( - screenData[index].values.toList()[0].toString(), + screenData[index].values.toList()[0].runtimeType == double + ? "${screenData[index].values.toList()[0].toStringAsFixed(2)}${widget.unit != null ? ' ${widget.unit}' : ''}" + : "${screenData[index].values.toList()[0].toString()}${widget.unit != null ? ' ${widget.unit}' : ''}", style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant ), @@ -181,7 +188,7 @@ class _TopItemsModalState extends State { ), const SizedBox(height: 5), ], - Row( + if (widget.withProgressBar == true) Row( children: [ SizedBox( width: 50, diff --git a/lib/widgets/domain_options.dart b/lib/widgets/domain_options.dart index 3c4bb79..703f63f 100644 --- a/lib/widgets/domain_options.dart +++ b/lib/widgets/domain_options.dart @@ -17,21 +17,21 @@ import 'package:adguard_home_manager/models/menu_option.dart'; class DomainOptions extends StatelessWidget { final bool isBlocked; - final bool? isClient; + final bool? isDomain; final String? item; final Widget child; final void Function() onTap; final BorderRadius? borderRadius; const DomainOptions({ - Key? key, + super.key, required this.isBlocked, - this.isClient, + this.isDomain, required this.item, required this.child, required this.onTap, this.borderRadius - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -80,12 +80,12 @@ class DomainOptions extends StatelessWidget { List generateOptions() { return [ - if (isClient != true && isBlocked == true) MenuOption( + if (isDomain == true && isBlocked == true) MenuOption( title: AppLocalizations.of(context)!.unblock, icon: Icons.check, action: () => blockUnblock(item!, 'unblock') ), - if (isClient != true && isBlocked == false) MenuOption( + if (isDomain == true && isBlocked == false) MenuOption( title: AppLocalizations.of(context)!.block, icon: Icons.block, action: () => blockUnblock(item!, 'block') From bc8aa3b670713409d591d320c48a254908fbf4d0 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 25 Nov 2023 19:14:00 +0100 Subject: [PATCH 121/177] Updated app version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index d4f6f8e..e311cd9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.11.3+108 +version: 2.12.0-beta.1+109 environment: sdk: '>=2.18.1 <3.0.0' From 63d57245a7b6e3153aec9325b3ee0a6e8482eeef Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 26 Nov 2023 05:21:35 +0100 Subject: [PATCH 122/177] Improved top items lists, improved menus and fixed units --- lib/models/menu_option.dart | 2 +- .../clients/client/active_client_tile.dart | 80 ++---- .../clients/client/added_client_tile.dart | 116 ++++----- lib/screens/clients/clients.dart | 58 +++-- lib/screens/clients/clients_lists.dart | 4 +- .../filters/filters_triple_column.dart | 2 +- lib/screens/filters/list_options_menu.dart | 8 +- lib/screens/home/home.dart | 97 +------ lib/screens/home/top_items/row_item.dart | 46 +--- .../top_items/top_item_expansion_panel.dart | 193 -------------- lib/screens/home/top_items/top_items.dart | 216 ++++++++++++---- .../home/top_items/top_items_lists.dart | 239 ++++++++++++++++++ lib/screens/logs/log_tile.dart | 93 ++++++- lib/screens/logs/logs.dart | 3 +- lib/screens/top_items/top_items.dart | 59 ++--- lib/screens/top_items/top_items_modal.dart | 61 ++--- lib/widgets/domain_options.dart | 136 ---------- lib/widgets/options_menu.dart | 63 +++++ lib/widgets/options_modal.dart | 4 +- 19 files changed, 713 insertions(+), 767 deletions(-) delete mode 100644 lib/screens/home/top_items/top_item_expansion_panel.dart create mode 100644 lib/screens/home/top_items/top_items_lists.dart delete mode 100644 lib/widgets/domain_options.dart create mode 100644 lib/widgets/options_menu.dart diff --git a/lib/models/menu_option.dart b/lib/models/menu_option.dart index e898514..b89f8d9 100644 --- a/lib/models/menu_option.dart +++ b/lib/models/menu_option.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class MenuOption { final IconData? icon; final String title; - final void Function() action; + final void Function(dynamic) action; final bool? disabled; const MenuOption({ diff --git a/lib/screens/clients/client/active_client_tile.dart b/lib/screens/clients/client/active_client_tile.dart index 26cb30f..76c928c 100644 --- a/lib/screens/clients/client/active_client_tile.dart +++ b/lib/screens/clients/client/active_client_tile.dart @@ -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, ), ); } diff --git a/lib/screens/clients/client/added_client_tile.dart b/lib/screens/clients/client/added_client_tile.dart index 53a481a..a33578f 100644 --- a/lib/screens/clients/client/added_client_tile.dart +++ b/lib/screens/clients/client/added_client_tile.dart @@ -1,15 +1,16 @@ 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/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; @@ -29,60 +30,48 @@ class AddedClientTile extends StatelessWidget { required this.splitView, }); + @override + State createState() => _AddedClientTileState(); +} + +class _AddedClientTileState extends State { + bool _isHover = false; + @override Widget build(BuildContext context) { final appConfigProvider = Provider.of(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) => [ - if (onEdit != null) CustomListTile( - title: AppLocalizations.of(context)!.edit, - icon: Icons.edit_rounded, - onTap: () { - Navigator.pop(context); - onEdit!(client); - } - ), - CustomListTile( - title: AppLocalizations.of(context)!.delete, - icon: Icons.delete_rounded, - onTap: () { - Navigator.pop(context); - onDelete(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( @@ -94,7 +83,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, @@ -107,7 +96,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 @@ -119,7 +108,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 @@ -131,7 +120,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 @@ -143,7 +132,7 @@ class AddedClientTile extends StatelessWidget { Icon( Icons.search_rounded, size: 19, - color: client.safeSearch != null && client.safeSearch!.enabled == true + color: widget.client.safeSearch != null && widget.client.safeSearch!.enabled == true ? appConfigProvider.useThemeColorForStatus == true ? Theme.of(context).colorScheme.primary : Colors.green @@ -159,6 +148,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, + ) + ] ], ) ), @@ -168,37 +165,30 @@ class AddedClientTile extends StatelessWidget { ); } else { - return ContextMenuArea( - builder: (context) => [ - if (onEdit != null) 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 ), @@ -209,7 +199,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 @@ -221,7 +211,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 @@ -233,7 +223,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 @@ -245,7 +235,7 @@ class AddedClientTile extends StatelessWidget { Icon( Icons.search_rounded, size: 19, - color: client.safeSearch != null && client.safeSearch!.enabled == true + color: widget.client.safeSearch != null && widget.client.safeSearch!.enabled == true ? appConfigProvider.useThemeColorForStatus == true ? Theme.of(context).colorScheme.primary : Colors.green diff --git a/lib/screens/clients/clients.dart b/lib/screens/clients/clients.dart index 80393a8..3d494ae 100644 --- a/lib/screens/clients/clients.dart +++ b/lib/screens/clients/clients.dart @@ -7,7 +7,7 @@ import 'package:adguard_home_manager/screens/clients/clients_lists.dart'; import 'package:adguard_home_manager/models/clients.dart'; class Clients extends StatefulWidget { - const Clients({Key? key}) : super(key: key); + const Clients({super.key}); @override State createState() => _ClientsState(); @@ -20,36 +20,38 @@ class _ClientsState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { - return 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 + 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: const ClientsLists( - splitView: true, - ) - ); - } - else { - return const ClientsLists( - splitView: false, - ); - } - }, + child: const ClientsLists( + splitView: true, + ) + ); + } + else { + return const ClientsLists( + splitView: false, + ); + } + }, + ), ); } } \ No newline at end of file diff --git a/lib/screens/clients/clients_lists.dart b/lib/screens/clients/clients_lists.dart index 0e518ce..bdc56b8 100644 --- a/lib/screens/clients/clients_lists.dart +++ b/lib/screens/clients/clients_lists.dart @@ -18,9 +18,9 @@ class ClientsLists extends StatefulWidget { final bool splitView; const ClientsLists({ - Key? key, + super.key, required this.splitView, - }) : super(key: key); + }); @override State createState() => _ClientsListsState(); diff --git a/lib/screens/filters/filters_triple_column.dart b/lib/screens/filters/filters_triple_column.dart index 6ecdd8f..2042068 100644 --- a/lib/screens/filters/filters_triple_column.dart +++ b/lib/screens/filters/filters_triple_column.dart @@ -290,7 +290,7 @@ class FiltersTripleColumn extends StatelessWidget { MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: () => copyToClipboard( + action: (_) => copyToClipboard( value: filteringProvider.filtering!.userRules[index], successMessage: AppLocalizations.of(context)!.copiedClipboard, ) diff --git a/lib/screens/filters/list_options_menu.dart b/lib/screens/filters/list_options_menu.dart index 2e47d36..f4337a1 100644 --- a/lib/screens/filters/list_options_menu.dart +++ b/lib/screens/filters/list_options_menu.dart @@ -155,12 +155,12 @@ class ListOptionsMenu extends StatelessWidget { icon: list.enabled == true ? Icons.gpp_bad_rounded : Icons.verified_user_rounded, - action: enableDisable + action: (_) => enableDisable() ), MenuOption( title: AppLocalizations.of(context)!.copyListUrl, icon: Icons.copy_rounded, - action: () => copyToClipboard( + action: (_) => copyToClipboard( value: list.url, successMessage: AppLocalizations.of(context)!.listUrlCopied ) @@ -168,12 +168,12 @@ class ListOptionsMenu extends StatelessWidget { MenuOption( title: AppLocalizations.of(context)!.openListUrl, icon: Icons.open_in_browser_rounded, - action: () => openUrl(list.url) + action: (_) => openUrl(list.url) ), MenuOption( title: AppLocalizations.of(context)!.selectionMode, icon: Icons.check_rounded, - action: openSelectionMode + action: (_) => openSelectionMode() ), ] ) diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index ec48e54..9e5de00 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -8,10 +8,10 @@ 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/top_items.dart'; import 'package:adguard_home_manager/screens/home/chart.dart'; import 'package:adguard_home_manager/functions/number_format.dart'; @@ -21,7 +21,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 createState() => _HomeState(); @@ -236,97 +236,4 @@ class _HomeState extends State { ), ); } -} - -class TopItemsLists extends StatelessWidget { - final List order; - - const TopItemsLists({ - super.key, - required this.order, - }); - - @override - Widget build(BuildContext context) { - final statusProvider = Provider.of(context); - - List bottom = [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Divider( - thickness: 1, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.2), - ), - ), - const SizedBox(height: 16), - ]; - - return Column( - children: order.asMap().entries.map((item) { - switch (item.value) { - case HomeTopItems.queriedDomains: - return Column( - children: [ - TopItems( - label: AppLocalizations.of(context)!.topQueriedDomains, - type: HomeTopItems.queriedDomains, - ), - if (item.key < order.length - 1) ...bottom - ], - ); - - case HomeTopItems.blockedDomains: - return Column( - children: [ - TopItems( - label: AppLocalizations.of(context)!.topBlockedDomains, - type: HomeTopItems.blockedDomains, - ), - if (item.key < order.length - 1) ...bottom - ], - ); - - case HomeTopItems.recurrentClients: - return Column( - children: [ - TopItems( - label: AppLocalizations.of(context)!.topClients, - type: HomeTopItems.recurrentClients, - ), - if (item.key < order.length - 1) ...bottom - ], - ); - - case HomeTopItems.topUpstreams: - return statusProvider.serverStatus!.stats.topUpstreamResponses != null - ? Column( - children: [ - TopItems( - label: AppLocalizations.of(context)!.topUpstreams, - type: HomeTopItems.topUpstreams, - ), - if (item.key < order.length - 1) ...bottom - ], - ) - : const SizedBox(); - - case HomeTopItems.avgUpstreamResponseTime: - return statusProvider.serverStatus!.stats.topUpstreamsAvgTime != null - ? Column( - children: [ - TopItems( - label: AppLocalizations.of(context)!.averageUpstreamResponseTime, - type: HomeTopItems.avgUpstreamResponseTime, - ), - if (item.key < order.length - 1) ...bottom - ], - ) - : const SizedBox(); - - default: - return const SizedBox(); - } - }).toList(), - ); - } } \ No newline at end of file diff --git a/lib/screens/home/top_items/row_item.dart b/lib/screens/home/top_items/row_item.dart index 4e3c184..569d30c 100644 --- a/lib/screens/home/top_items/row_item.dart +++ b/lib/screens/home/top_items/row_item.dart @@ -2,12 +2,10 @@ 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/domain_options.dart'; +import 'package:adguard_home_manager/widgets/options_menu.dart'; +import 'package:adguard_home_manager/models/menu_option.dart'; import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/models/applied_filters.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; -import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; class RowItem extends StatefulWidget { @@ -18,6 +16,8 @@ class RowItem extends StatefulWidget { final bool clients; final bool showColor; final String? unit; + final List options; + final void Function(dynamic)? onTapEntry; const RowItem({ super.key, @@ -27,6 +27,8 @@ class RowItem extends StatefulWidget { required this.number, required this.clients, required this.showColor, + required this.options, + this.onTapEntry, this.unit, }); @@ -80,8 +82,6 @@ class _RowItemState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { final statusProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - final logsProvider = Provider.of(context); String? name; if (widget.clients == true) { @@ -94,36 +94,10 @@ class _RowItemState extends State with TickerProviderStateMixin { return Material( color: Colors.transparent, - child: DomainOptions( - item: widget.domain, - isDomain: widget.type == HomeTopItems.queriedDomains || widget.type == HomeTopItems.blockedDomains, - isBlocked: widget.type == HomeTopItems.blockedDomains, - onTap: () { - if (widget.type == HomeTopItems.queriedDomains || widget.type == HomeTopItems.blockedDomains) { - logsProvider.setSearchText(widget.domain); - logsProvider.setSelectedClients(null); - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: 'all', - searchText: widget.domain, - clients: null - ) - ); - appConfigProvider.setSelectedScreen(2); - } - else if (widget.type == HomeTopItems.recurrentClients) { - logsProvider.setSearchText(null); - logsProvider.setSelectedClients([widget.domain]); - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: 'all', - searchText: null, - clients: [widget.domain] - ) - ); - appConfigProvider.setSelectedScreen(2); - } - }, + child: OptionsMenu( + value: widget.domain, + options: widget.options, + onTap: widget.onTapEntry, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 20, diff --git a/lib/screens/home/top_items/top_item_expansion_panel.dart b/lib/screens/home/top_items/top_item_expansion_panel.dart deleted file mode 100644 index eb62d01..0000000 --- a/lib/screens/home/top_items/top_item_expansion_panel.dart +++ /dev/null @@ -1,193 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:adguard_home_manager/widgets/custom_pie_chart.dart'; -import 'package:adguard_home_manager/screens/home/top_items/row_item.dart'; - -import 'package:adguard_home_manager/constants/enums.dart'; - -class TopItemExpansionPanel extends StatefulWidget { - final HomeTopItems type; - final String label; - final List> data; - final Map chartData; - final bool withChart; - - const TopItemExpansionPanel({ - super.key, - required this.type, - required this.label, - required this.data, - required this.chartData, - required this.withChart - }); - - @override - State createState() => _TopItemExpansionPanelState(); -} - -class _TopItemExpansionPanelState extends State { - bool _showChart = true; - - final colors = [ - Colors.red, - Colors.green, - Colors.blue, - Colors.orange, - Colors.teal, - Colors.grey - ]; - - @override - Widget build(BuildContext context) { - final width = MediaQuery.of(context).size.width; - - if (widget.withChart == true) { - return Column( - children: [ - ExpansionPanelList( - expandedHeaderPadding: const EdgeInsets.all(0), - elevation: 0, - expansionCallback: (_, isExpanded) => setState(() => _showChart = isExpanded), - animationDuration: const Duration(milliseconds: 250), - children: [ - ExpansionPanel( - headerBuilder: (context, isExpanded) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Row( - mainAxisAlignment: width <= 700 - ? MainAxisAlignment.spaceBetween - : MainAxisAlignment.center, - children: [ - Flexible( - child: Text( - widget.label, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface - ), - ), - ), - ], - ), - ), - body: Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Column( - children: [ - SizedBox( - height: 150, - child: CustomPieChart( - data: widget.chartData, - colors: colors - ) - ), - const SizedBox(height: 16), - ], - ), - ), - isExpanded: _showChart - ), - ], - ), - Padding( - padding: const EdgeInsets.only(top: 8), - child: _ItemsList( - colors: colors, - data: widget.data, - clients: widget.type == HomeTopItems.recurrentClients, - type: widget.type, - showChart: _showChart, - unit: widget.type == HomeTopItems.avgUpstreamResponseTime ? 'ms' : null, - ), - ), - if (widget.type != HomeTopItems.avgUpstreamResponseTime) OthersRowItem( - items: widget.data, - showColor: _showChart, - ), - const SizedBox(height: 16), - ], - ); - } - else { - return Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 18), - child: Row( - mainAxisAlignment: width <= 700 - ? MainAxisAlignment.spaceBetween - : MainAxisAlignment.center, - children: [ - Flexible( - child: Text( - widget.label, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface - ), - ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.only(top: 16), - child: _ItemsList( - colors: colors, - data: widget.data, - clients: widget.type == HomeTopItems.recurrentClients, - type: widget.type, - showChart: false, - unit: widget.type == HomeTopItems.avgUpstreamResponseTime ? 'ms' : null, - ), - ), - if (widget.type != HomeTopItems.avgUpstreamResponseTime) OthersRowItem( - items: widget.data, - showColor: false, - ), - const SizedBox(height: 16), - ], - ); - } - } -} - -class _ItemsList extends StatelessWidget { - final List colors; - final List> data; - final bool? clients; - final HomeTopItems type; - final bool showChart; - final String? unit; - - const _ItemsList({ - required this.colors, - required this.data, - required this.clients, - required this.type, - required this.showChart, - this.unit, - }); - - @override - Widget build(BuildContext context) { - return Column( - children: data.sublist( - 0, data.length > 5 ? 5 : data.length - ).asMap().entries.map((e) => RowItem( - clients: clients ?? false, - domain: e.value.keys.toList()[0], - number: e.value.values.toList()[0].runtimeType == double - ? "${e.value.values.toList()[0].toStringAsFixed(2)}${unit != null ? ' $unit' : ''}" - : "${e.value.values.toList()[0].toString()}${unit != null ? ' $unit' : ''}", - type: type, - chartColor: colors[e.key], - showColor: showChart, - )).toList() - ); - } -} \ No newline at end of file diff --git a/lib/screens/home/top_items/top_items.dart b/lib/screens/home/top_items/top_items.dart index 6276d74..00fa63c 100644 --- a/lib/screens/home/top_items/top_items.dart +++ b/lib/screens/home/top_items/top_items.dart @@ -7,23 +7,34 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/home/top_items/row_item.dart'; -import 'package:adguard_home_manager/screens/home/top_items/top_item_expansion_panel.dart'; import 'package:adguard_home_manager/screens/top_items/top_items_modal.dart'; import 'package:adguard_home_manager/screens/top_items/top_items.dart'; import 'package:adguard_home_manager/widgets/custom_pie_chart.dart'; +import 'package:adguard_home_manager/models/menu_option.dart'; import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class TopItems extends StatefulWidget { final HomeTopItems type; final String label; + final List> data; + final bool withChart; + final bool withProgressBar; + final String Function(dynamic) buildValue; + final List menuOptions; + final void Function(dynamic)? onTapEntry; const TopItems({ super.key, required this.type, required this.label, + required this.data, + required this.withChart, + required this.withProgressBar, + required this.buildValue, + required this.menuOptions, + this.onTapEntry, }); @override @@ -50,47 +61,21 @@ class _TopItemsState extends State { @override Widget build(BuildContext context) { - final statusProvider = Provider.of(context); - final width = MediaQuery.of(context).size.width; - List> generateData() { - switch (widget.type) { - case HomeTopItems.queriedDomains: - return statusProvider.serverStatus!.stats.topQueriedDomains; - - case HomeTopItems.blockedDomains: - return statusProvider.serverStatus!.stats.topBlockedDomains; - - case HomeTopItems.recurrentClients: - return statusProvider.serverStatus!.stats.topClients; - - case HomeTopItems.topUpstreams: - return statusProvider.serverStatus!.stats.topUpstreamResponses ?? []; - - case HomeTopItems.avgUpstreamResponseTime: - return statusProvider.serverStatus!.stats.topUpstreamsAvgTime ?? []; - - default: - return []; - } - } - - final data = generateData(); - final withChart = widget.type != HomeTopItems.avgUpstreamResponseTime; Map chartData() { Map values = {}; - data.sublist(0, data.length > 5 ? 5 : data.length).forEach((element) { + widget.data.sublist(0, widget.data.length > 5 ? 5 : widget.data.length).forEach((element) { values = { ...values, element.keys.first: element.values.first.toDouble() }; }); - if (data.length > 5) { + if (widget.data.length > 5) { final int rest = List.from( - data.sublist(5, data.length).map((e) => e.values.first.toInt()) + widget.data.sublist(5, widget.data.length).map((e) => e.values.first.toInt()) ).reduce((a, b) => a + b); values = { ...values, @@ -117,8 +102,8 @@ class _TopItemsState extends State { return SizedBox( child: Column( children: [ - if (data.isEmpty) noItems, - if (data.isNotEmpty && width > 700) Padding( + if (widget.data.isEmpty) noItems, + if (widget.data.isNotEmpty && width > 700) Padding( padding: EdgeInsets.only(bottom: withChart == false ? 16 : 0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, @@ -157,14 +142,16 @@ class _TopItemsState extends State { ), _ItemsList( colors: colors, - data: data, + data: widget.data, clients: widget.type == HomeTopItems.recurrentClients, type: widget.type, showChart: withChart == true ? _showChart : false, - unit: widget.type == HomeTopItems.avgUpstreamResponseTime ? 'ms' : null, + buildValue: widget.buildValue, + menuOptions: widget.menuOptions, + onTapEntry: widget.onTapEntry, ), if (withChart == true) OthersRowItem( - items: data, + items: widget.data, showColor: true, ) ] @@ -173,15 +160,128 @@ class _TopItemsState extends State { ], ), ), - if (data.isNotEmpty && width <= 700) TopItemExpansionPanel( - type: widget.type, - label: widget.label, - data: data, - chartData: chartData(), - withChart: withChart + if (widget.data.isNotEmpty && width <= 700) Builder( + builder: (context) { + if (widget.withChart == true) { + return Column( + children: [ + ExpansionPanelList( + expandedHeaderPadding: const EdgeInsets.all(0), + elevation: 0, + expansionCallback: (_, isExpanded) => setState(() => _showChart = isExpanded), + animationDuration: const Duration(milliseconds: 250), + children: [ + ExpansionPanel( + headerBuilder: (context, isExpanded) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Row( + mainAxisAlignment: width <= 700 + ? MainAxisAlignment.spaceBetween + : MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + widget.label, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ), + ], + ), + ), + body: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + children: [ + SizedBox( + height: 150, + child: CustomPieChart( + data: chartData(), + colors: colors + ) + ), + const SizedBox(height: 16), + ], + ), + ), + isExpanded: _showChart + ), + ], + ), + Padding( + padding: const EdgeInsets.only(top: 8), + child: _ItemsList( + colors: colors, + data: widget.data, + clients: widget.type == HomeTopItems.recurrentClients, + type: widget.type, + showChart: _showChart, + buildValue: widget.buildValue, + menuOptions: widget.menuOptions, + onTapEntry: widget.onTapEntry, + ), + ), + if (widget.withChart == true) OthersRowItem( + items: widget.data, + showColor: _showChart, + ), + const SizedBox(height: 16), + ], + ); + } + else { + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 18), + child: Row( + mainAxisAlignment: width <= 700 + ? MainAxisAlignment.spaceBetween + : MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + widget.label, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(top: 16), + child: _ItemsList( + colors: colors, + data: widget.data, + clients: widget.type == HomeTopItems.recurrentClients, + type: widget.type, + showChart: false, + buildValue: widget.buildValue, + menuOptions: widget.menuOptions, + onTapEntry: widget.onTapEntry, + ), + ), + if (widget.withChart == true) OthersRowItem( + items: widget.data, + showColor: false, + ), + const SizedBox(height: 16), + ], + ); + } + }, ), - if (data.length > 5) ...[ + if (widget.data.length > 5) ...[ Padding( padding: const EdgeInsets.only(right: 20), child: Row( @@ -197,9 +297,11 @@ class _TopItemsState extends State { type: widget.type, title: widget.label, isClient: widget.type == HomeTopItems.recurrentClients, - data: generateData(), - withProgressBar: widget.type != HomeTopItems.avgUpstreamResponseTime, - unit: widget.type == HomeTopItems.avgUpstreamResponseTime ? 'ms' : null, + data: widget.data, + withProgressBar: widget.withProgressBar, + buildValue: widget.buildValue, + options: widget.menuOptions, + onTapEntry: widget.onTapEntry, ) ) } @@ -210,9 +312,11 @@ class _TopItemsState extends State { type: widget.type, title: widget.label, isClient: widget.type == HomeTopItems.recurrentClients, - data: generateData(), - withProgressBar: widget.type != HomeTopItems.avgUpstreamResponseTime, - unit: widget.type == HomeTopItems.avgUpstreamResponseTime ? 'ms' : null, + data: widget.data, + withProgressBar: widget.withProgressBar, + buildValue: widget.buildValue, + menuOptions: widget.menuOptions, + onTapEntry: widget.onTapEntry, ) ) ) @@ -247,7 +351,9 @@ class _ItemsList extends StatelessWidget { final bool? clients; final HomeTopItems type; final bool showChart; - final String? unit; + final String Function(dynamic) buildValue; + final List menuOptions; + final void Function(dynamic)? onTapEntry; const _ItemsList({ required this.colors, @@ -255,7 +361,9 @@ class _ItemsList extends StatelessWidget { required this.clients, required this.type, required this.showChart, - this.unit, + required this.buildValue, + required this.menuOptions, + this.onTapEntry, }); @override @@ -266,12 +374,12 @@ class _ItemsList extends StatelessWidget { ).asMap().entries.map((e) => RowItem( clients: clients ?? false, domain: e.value.keys.toList()[0], - number: e.value.values.toList()[0].runtimeType == double - ? "${e.value.values.toList()[0].toStringAsFixed(2)}${unit != null ? ' $unit' : ''}" - : "${e.value.values.toList()[0].toString()}${unit != null ? ' $unit' : ''}", + number: buildValue(e.value.values.toList()[0]), type: type, chartColor: colors[e.key], showColor: showChart, + options: menuOptions, + onTapEntry: onTapEntry, )).toList() ); } diff --git a/lib/screens/home/top_items/top_items_lists.dart b/lib/screens/home/top_items/top_items_lists.dart new file mode 100644 index 0000000..2f8820d --- /dev/null +++ b/lib/screens/home/top_items/top_items_lists.dart @@ -0,0 +1,239 @@ +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/home/top_items/top_items.dart'; + +import 'package:adguard_home_manager/functions/number_format.dart'; +import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/classes/process_modal.dart'; +import 'package:adguard_home_manager/models/applied_filters.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/providers/logs_provider.dart'; +import 'package:adguard_home_manager/functions/copy_clipboard.dart'; +import 'package:adguard_home_manager/models/menu_option.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; + +class TopItemsLists extends StatelessWidget { + final List order; + + const TopItemsLists({ + super.key, + required this.order, + }); + + @override + Widget build(BuildContext context) { + final statusProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); + final logsProvider = Provider.of(context); + + List bottom = [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Divider( + thickness: 1, + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.2), + ), + ), + const SizedBox(height: 16), + ]; + + void filterDomainLogs({required String value}) { + logsProvider.setSearchText(value); + logsProvider.setSelectedClients(null); + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: 'all', + searchText: value, + clients: null + ) + ); + appConfigProvider.setSelectedScreen(2); + } + + void filterClientLogs({required String value}) { + logsProvider.setSearchText(null); + logsProvider.setSelectedClients([value]); + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: 'all', + searchText: null, + clients: [value] + ) + ); + appConfigProvider.setSelectedScreen(2); + } + + void blockUnblock({required String domain, required String newStatus}) async { + final ProcessModal processModal = ProcessModal(); + processModal.open(AppLocalizations.of(context)!.savingUserFilters); + + final rules = await statusProvider.blockUnblockDomain( + domain: domain, + newStatus: newStatus + ); + + processModal.close(); + + if (!context.mounted) return; + if (rules == true) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.userFilteringRulesUpdated, + color: Colors.green + ); + } + else { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.userFilteringRulesNotUpdated, + color: Colors.red + ); + } + } + + void copyValueClipboard(value) { + copyToClipboard(value: value, successMessage: AppLocalizations.of(context)!.copiedClipboard); + } + + return Column( + children: order.asMap().entries.map((item) { + switch (item.value) { + case HomeTopItems.queriedDomains: + return Column( + children: [ + TopItems( + label: AppLocalizations.of(context)!.topQueriedDomains, + type: HomeTopItems.queriedDomains, + data: statusProvider.serverStatus?.stats.topQueriedDomains ?? [], + withChart: true, + withProgressBar: true, + buildValue: (v) => v.toString(), + menuOptions: [ + MenuOption( + title: AppLocalizations.of(context)!.blockDomain, + icon: Icons.block_rounded, + action: (v) => blockUnblock(domain: v.toString(), newStatus: 'block') + ), + MenuOption( + title: AppLocalizations.of(context)!.copyClipboard, + icon: Icons.copy_rounded, + action: copyValueClipboard + ), + ], + onTapEntry: (v) => filterDomainLogs(value: v.toString()), + ), + if (item.key < order.length - 1) ...bottom + ], + ); + + case HomeTopItems.blockedDomains: + return Column( + children: [ + TopItems( + label: AppLocalizations.of(context)!.topBlockedDomains, + type: HomeTopItems.blockedDomains, + data: statusProvider.serverStatus?.stats.topBlockedDomains ?? [], + withChart: true, + withProgressBar: true, + buildValue: (v) => v.toString(), + menuOptions: [ + MenuOption( + title: AppLocalizations.of(context)!.unblockDomain, + icon: Icons.check_rounded, + action: (v) => blockUnblock(domain: v, newStatus: 'unblock') + ), + MenuOption( + title: AppLocalizations.of(context)!.copyClipboard, + icon: Icons.copy_rounded, + action: copyValueClipboard + ) + ], + onTapEntry: (v) => filterDomainLogs(value: v), + ), + if (item.key < order.length - 1) ...bottom + ], + ); + + case HomeTopItems.recurrentClients: + return Column( + children: [ + TopItems( + label: AppLocalizations.of(context)!.topClients, + type: HomeTopItems.recurrentClients, + data: statusProvider.serverStatus?.stats.topClients ?? [], + withChart: true, + withProgressBar: true, + buildValue: (v) => v.toString(), + menuOptions: [ + MenuOption( + title: AppLocalizations.of(context)!.copyClipboard, + icon: Icons.copy_rounded, + action: copyValueClipboard + ) + ], + onTapEntry: (v) => filterClientLogs(value: v), + ), + if (item.key < order.length - 1) ...bottom + ], + ); + + case HomeTopItems.topUpstreams: + return statusProvider.serverStatus!.stats.topUpstreamResponses != null + ? Column( + children: [ + TopItems( + label: AppLocalizations.of(context)!.topUpstreams, + type: HomeTopItems.topUpstreams, + data: statusProvider.serverStatus?.stats.topUpstreamResponses ?? [], + withChart: true, + withProgressBar: true, + buildValue: (v) => v.toString(), + menuOptions: [ + MenuOption( + title: AppLocalizations.of(context)!.copyClipboard, + icon: Icons.copy_rounded, + action: copyValueClipboard + ) + ], + ), + if (item.key < order.length - 1) ...bottom + ], + ) + : const SizedBox(); + + case HomeTopItems.avgUpstreamResponseTime: + return statusProvider.serverStatus!.stats.topUpstreamsAvgTime != null + ? Column( + children: [ + TopItems( + label: AppLocalizations.of(context)!.averageUpstreamResponseTime, + type: HomeTopItems.avgUpstreamResponseTime, + data: statusProvider.serverStatus?.stats.topUpstreamsAvgTime ?? [], + withChart: false, + withProgressBar: false, + buildValue: (v) => "${doubleFormat(v*1000, Platform.localeName)} ms", + menuOptions: [ + MenuOption( + title: AppLocalizations.of(context)!.copyClipboard, + icon: Icons.copy_rounded, + action: copyValueClipboard + ) + ], + ), + if (item.key < order.length - 1) ...bottom + ], + ) + : const SizedBox(); + + default: + return const SizedBox(); + } + }).toList(), + ); + } +} \ No newline at end of file diff --git a/lib/screens/logs/log_tile.dart b/lib/screens/logs/log_tile.dart index 14c5262..95981e8 100644 --- a/lib/screens/logs/log_tile.dart +++ b/lib/screens/logs/log_tile.dart @@ -2,9 +2,15 @@ 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/domain_options.dart'; +import 'package:adguard_home_manager/widgets/options_menu.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; +import 'package:adguard_home_manager/classes/process_modal.dart'; +import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/functions/copy_clipboard.dart'; +import 'package:adguard_home_manager/models/menu_option.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/models/logs.dart'; @@ -20,7 +26,7 @@ class LogTile extends StatelessWidget { final bool twoColumns; const LogTile({ - Key? key, + super.key, required this.log, required this.length, required this.index, @@ -28,11 +34,12 @@ class LogTile extends StatelessWidget { required this.onLogTap, this.useAlwaysNormalTile, required this.twoColumns, - }) : super(key: key); + }); @override Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); + final statusProvider = Provider.of(context); Widget logStatusWidget({ required IconData icon, @@ -83,16 +90,63 @@ class LogTile extends StatelessWidget { } } + void blockUnblock({required String domain, required String newStatus}) async { + final ProcessModal processModal = ProcessModal(); + processModal.open(AppLocalizations.of(context)!.savingUserFilters); + + final rules = await statusProvider.blockUnblockDomain( + domain: domain, + newStatus: newStatus + ); + + processModal.close(); + + if (!context.mounted) return; + if (rules == true) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.userFilteringRulesUpdated, + color: Colors.green + ); + } + else { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.userFilteringRulesNotUpdated, + color: Colors.red + ); + } + } + + final domainBlocked = isDomainBlocked(log.reason); + if (twoColumns && !(useAlwaysNormalTile == true)) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: InkWell( borderRadius: BorderRadius.circular(28), - child: DomainOptions( - onTap: () => onLogTap(log), + child: OptionsMenu( + onTap: (_) => onLogTap(log), borderRadius: BorderRadius.circular(28), - item: log.question.name, - isBlocked: isDomainBlocked(log.reason), + options: [ + if (log.question.name != null) MenuOption( + title: domainBlocked == true + ? AppLocalizations.of(context)!.unblockDomain + : AppLocalizations.of(context)!.blockDomain, + icon: domainBlocked == true + ? Icons.check_rounded + : Icons.block_rounded, + action: (_) => blockUnblock( + domain: log.question.name!, + newStatus: domainBlocked == true ? 'unblock' : 'block' + ) + ), + MenuOption( + title: AppLocalizations.of(context)!.copyClipboard, + icon: Icons.copy_rounded, + action: (v) => copyToClipboard(value: v, successMessage: AppLocalizations.of(context)!.copiedClipboard) + ) + ], child: Container( width: double.maxFinite, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), @@ -250,10 +304,27 @@ class LogTile extends StatelessWidget { else { return Material( color: Colors.transparent, - child: DomainOptions( - onTap: () => onLogTap(log), - item: log.question.name, - isBlocked: isDomainBlocked(log.reason), + child: OptionsMenu( + onTap: (_) => onLogTap(log), + options: [ + if (log.question.name != null) MenuOption( + title: domainBlocked == true + ? AppLocalizations.of(context)!.unblockDomain + : AppLocalizations.of(context)!.blockDomain, + icon: domainBlocked == true + ? Icons.check_rounded + : Icons.block_rounded, + action: (_) => blockUnblock( + domain: log.question.name!, + newStatus: domainBlocked == true ? 'unblock' : 'block' + ) + ), + MenuOption( + title: AppLocalizations.of(context)!.copyClipboard, + icon: Icons.copy_rounded, + action: (v) => copyToClipboard(value: v, successMessage: AppLocalizations.of(context)!.copiedClipboard) + ) + ], child: Container( width: double.maxFinite, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), diff --git a/lib/screens/logs/logs.dart b/lib/screens/logs/logs.dart index c1510c3..757c949 100644 --- a/lib/screens/logs/logs.dart +++ b/lib/screens/logs/logs.dart @@ -1,11 +1,12 @@ // ignore_for_file: use_build_context_synchronously -import 'package:adguard_home_manager/models/logs.dart'; import 'package:flutter/material.dart'; import 'package:adguard_home_manager/screens/logs/logs_list.dart'; import 'package:adguard_home_manager/screens/logs/details/log_details_screen.dart'; +import 'package:adguard_home_manager/models/logs.dart'; + class Logs extends StatefulWidget { const Logs({Key? key}) : super(key: key); diff --git a/lib/screens/top_items/top_items.dart b/lib/screens/top_items/top_items.dart index 66ca288..498ce75 100644 --- a/lib/screens/top_items/top_items.dart +++ b/lib/screens/top_items/top_items.dart @@ -7,12 +7,11 @@ import 'package:percent_indicator/percent_indicator.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/widgets/domain_options.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/models/menu_option.dart'; import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/models/applied_filters.dart'; -import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/functions/number_format.dart'; @@ -24,7 +23,9 @@ class TopItemsScreen extends StatefulWidget { final bool? isClient; final List> data; final bool withProgressBar; - final String? unit; + final String Function(dynamic) buildValue; + final List menuOptions; + final void Function(dynamic)? onTapEntry; const TopItemsScreen({ super.key, @@ -33,7 +34,9 @@ class TopItemsScreen extends StatefulWidget { this.isClient, required this.data, required this.withProgressBar, - this.unit, + required this.buildValue, + required this.menuOptions, + this.onTapEntry, }); @override @@ -63,7 +66,6 @@ class _TopItemsScreenState extends State { Widget build(BuildContext context) { final statusProvider = Provider.of(context); final appConfigProvider = Provider.of(context); - final logsProvider = Provider.of(context); double total = 0; for (var element in data) { @@ -157,44 +159,19 @@ class _TopItemsScreenState extends State { } } - return DomainOptions( - item: screenData[index].keys.toList()[0], - isBlocked: widget.type == HomeTopItems.blockedDomains, - isDomain: widget.type == HomeTopItems.queriedDomains || widget.type == HomeTopItems.blockedDomains, - onTap: () { - if (widget.type == HomeTopItems.queriedDomains || widget.type == HomeTopItems.blockedDomains) { - logsProvider.setSearchText(screenData[index].keys.toList()[0]); - logsProvider.setSelectedClients(null); - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: 'all', - searchText: screenData[index].keys.toList()[0], - clients: null - ) - ); - appConfigProvider.setSelectedScreen(2); - Navigator.pop(context); - } - else if (widget.type == HomeTopItems.recurrentClients) { - logsProvider.setSearchText(null); - logsProvider.setSelectedClients([screenData[index].keys.toList()[0]]); - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: 'all', - searchText: null, - clients: [screenData[index].keys.toList()[0]] - ) - ); - appConfigProvider.setSelectedScreen(2); - Navigator.pop(context); - } - }, + return OptionsMenu( + value: screenData[index].keys.toList()[0], + options: widget.menuOptions, + onTap: widget.onTapEntry != null + ? (v) { + widget.onTapEntry!(v); + Navigator.pop(context); + } + : null, child: CustomListTile( title: screenData[index].keys.toList()[0], trailing: Text( - screenData[index].values.toList()[0].runtimeType == double - ? "${screenData[index].values.toList()[0].toStringAsFixed(2)}${widget.unit != null ? ' ${widget.unit}' : ''}" - : "${screenData[index].values.toList()[0].toString()}${widget.unit != null ? ' ${widget.unit}' : ''}", + widget.buildValue(screenData[index].values.toList()[0]), style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant ), diff --git a/lib/screens/top_items/top_items_modal.dart b/lib/screens/top_items/top_items_modal.dart index 1325c79..36ea793 100644 --- a/lib/screens/top_items/top_items_modal.dart +++ b/lib/screens/top_items/top_items_modal.dart @@ -7,15 +7,13 @@ import 'package:percent_indicator/percent_indicator.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/widgets/domain_options.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/models/menu_option.dart'; import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/models/applied_filters.dart'; -import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/functions/number_format.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; class TopItemsModal extends StatefulWidget { final HomeTopItems type; @@ -23,7 +21,9 @@ class TopItemsModal extends StatefulWidget { final bool? isClient; final List> data; final bool withProgressBar; - final String? unit; + final String Function(dynamic) buildValue; + final List options; + final void Function(dynamic)? onTapEntry; const TopItemsModal({ super.key, @@ -32,7 +32,9 @@ class TopItemsModal extends StatefulWidget { this.isClient, required this.data, required this.withProgressBar, - this.unit, + required this.buildValue, + required this.options, + this.onTapEntry, }); @override @@ -61,8 +63,6 @@ class _TopItemsModalState extends State { @override Widget build(BuildContext context) { final statusProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - final logsProvider = Provider.of(context); double total = 0; for (var element in data) { @@ -133,44 +133,19 @@ class _TopItemsModalState extends State { } } - return DomainOptions( - isBlocked: widget.type == HomeTopItems.blockedDomains, - isDomain: widget.type == HomeTopItems.queriedDomains || widget.type == HomeTopItems.blockedDomains, - item: screenData[index].keys.toList()[0], - onTap: () { - if (widget.type == HomeTopItems.queriedDomains || widget.type == HomeTopItems.blockedDomains) { - logsProvider.setSearchText(screenData[index].keys.toList()[0]); - logsProvider.setSelectedClients(null); - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: 'all', - searchText: screenData[index].keys.toList()[0], - clients: null - ) - ); - appConfigProvider.setSelectedScreen(2); - Navigator.pop(context); - } - else if (widget.type == HomeTopItems.recurrentClients) { - logsProvider.setSearchText(null); - logsProvider.setSelectedClients([screenData[index].keys.toList()[0]]); - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: 'all', - searchText: null, - clients: [screenData[index].keys.toList()[0]] - ) - ); - appConfigProvider.setSelectedScreen(2); - Navigator.pop(context); - } - }, + return OptionsMenu( + options: widget.options, + value: screenData[index].keys.toList()[0], + onTap: widget.onTapEntry != null + ? (v) { + widget.onTapEntry!(v); + Navigator.pop(context); + } + : null, child: CustomListTile( title: screenData[index].keys.toList()[0], trailing: Text( - screenData[index].values.toList()[0].runtimeType == double - ? "${screenData[index].values.toList()[0].toStringAsFixed(2)}${widget.unit != null ? ' ${widget.unit}' : ''}" - : "${screenData[index].values.toList()[0].toString()}${widget.unit != null ? ' ${widget.unit}' : ''}", + widget.buildValue(screenData[index].values.toList()[0]), style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant ), diff --git a/lib/widgets/domain_options.dart b/lib/widgets/domain_options.dart deleted file mode 100644 index 703f63f..0000000 --- a/lib/widgets/domain_options.dart +++ /dev/null @@ -1,136 +0,0 @@ -// ignore_for_file: use_build_context_synchronously - -import 'package:flutter/material.dart'; -import 'package:contextmenu/contextmenu.dart'; -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/widgets/options_modal.dart'; -import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; - -import 'package:adguard_home_manager/functions/snackbar.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'; -import 'package:adguard_home_manager/models/menu_option.dart'; - -class DomainOptions extends StatelessWidget { - final bool isBlocked; - final bool? isDomain; - final String? item; - final Widget child; - final void Function() onTap; - final BorderRadius? borderRadius; - - const DomainOptions({ - super.key, - required this.isBlocked, - this.isDomain, - required this.item, - required this.child, - required this.onTap, - this.borderRadius - }); - - @override - Widget build(BuildContext context) { - final statusProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - - void blockUnblock(String domain, String newStatus) async { - final ProcessModal processModal = ProcessModal(); - processModal.open(AppLocalizations.of(context)!.savingUserFilters); - - final rules = await statusProvider.blockUnblockDomain( - domain: domain, - newStatus: newStatus - ); - - processModal.close(); - - if (rules == true) { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.userFilteringRulesUpdated, - color: Colors.green - ); - } - else { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.userFilteringRulesNotUpdated, - color: Colors.red - - ); - } - } - - void copyDomainClipboard(String domain) async { - await Clipboard.setData( - ClipboardData(text: domain) - ); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(AppLocalizations.of(context)!.domainCopiedClipboard), - backgroundColor: Colors.green, - ) - ); - } - - List generateOptions() { - return [ - if (isDomain == true && isBlocked == true) MenuOption( - title: AppLocalizations.of(context)!.unblock, - icon: Icons.check, - action: () => blockUnblock(item!, 'unblock') - ), - if (isDomain == true && isBlocked == false) MenuOption( - title: AppLocalizations.of(context)!.block, - icon: Icons.block, - action: () => blockUnblock(item!, 'block') - ), - MenuOption( - title: AppLocalizations.of(context)!.copyClipboard, - icon: Icons.copy, - action: () => copyDomainClipboard(item!) - ), - ]; - } - - void openOptionsModal() { - showDialog( - context: context, - builder: (context) => OptionsModal( - options: generateOptions(), - ) - ); - } - - if (item != null) { - return Material( - color: Colors.transparent, - borderRadius: borderRadius, - child: ContextMenuArea( - builder: (context) => generateOptions().map((opt) => CustomListTile( - title: opt.title, - icon: opt.icon, - onTap: () { - opt.action(); - Navigator.pop(context); - }, - )).toList(), - child: InkWell( - onTap: onTap, - onLongPress: openOptionsModal, - borderRadius: borderRadius, - child: child, - ), - ), - ); - } - else { - return child; - } - } -} \ No newline at end of file diff --git a/lib/widgets/options_menu.dart b/lib/widgets/options_menu.dart new file mode 100644 index 0000000..34b755b --- /dev/null +++ b/lib/widgets/options_menu.dart @@ -0,0 +1,63 @@ +import 'dart:io'; + +import 'package:contextmenu/contextmenu.dart'; +import 'package:flutter/material.dart'; + +import 'package:adguard_home_manager/widgets/options_modal.dart'; +import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; + +import 'package:adguard_home_manager/models/menu_option.dart'; + +class OptionsMenu extends StatelessWidget { + final Widget child; + final List options; + final dynamic value; + final BorderRadius? borderRadius; + final void Function(dynamic)? onTap; + + const OptionsMenu({ + super.key, + required this.child, + required this.options, + this.value, + this.borderRadius, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + void openOptionsModal() { + showDialog( + context: context, + builder: (context) => OptionsModal( + options: options, + value: value + ) + ); + } + + return Material( + color: Colors.transparent, + child: ContextMenuArea( + builder: (context) => options.map((opt) => CustomListTile( + title: opt.title, + icon: opt.icon, + onTap: () { + opt.action(value); + Navigator.pop(context); + }, + )).toList(), + child: InkWell( + onTap: onTap != null + ? () => onTap!(value) + : null, + onLongPress: (Platform.isAndroid || Platform.isIOS) + ? () => openOptionsModal() + : null, + borderRadius: borderRadius, + child: child, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/options_modal.dart b/lib/widgets/options_modal.dart index 75a55a5..102f7ca 100644 --- a/lib/widgets/options_modal.dart +++ b/lib/widgets/options_modal.dart @@ -6,10 +6,12 @@ import 'package:adguard_home_manager/widgets/custom_list_tile_dialog.dart'; class OptionsModal extends StatelessWidget { final List options; + final dynamic value; const OptionsModal({ super.key, required this.options, + this.value, }); @override @@ -43,7 +45,7 @@ class OptionsModal extends StatelessWidget { icon: opt.icon, onTap: () { Navigator.pop(context); - opt.action(); + opt.action(value); }, )).toList() ), From 450390edbaa45047a15745a4b49d2b509a9d0d41 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 26 Nov 2023 05:22:50 +0100 Subject: [PATCH 123/177] Updated app version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index e311cd9..8770025 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.12.0-beta.1+109 +version: 2.12.0-beta.2+110 environment: sdk: '>=2.18.1 <3.0.0' From 0c0f78c8d8f026d6502943cd17b2ccd0beef7195 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 26 Nov 2023 14:51:28 +0100 Subject: [PATCH 124/177] Updated turkish translation --- lib/l10n/app_tr.arb | 84 +++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index 1fdb739..06c3b08 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -176,7 +176,7 @@ "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", + "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", @@ -192,17 +192,17 @@ "enableFiltering": "Filtrelemeyi etkinleştir", "enableSafeBrowsing": "Güvenli gezintiyi etkinleştir", "enableParentalControl": "Ebeveyn kontrolünü etkinleştir", - "enableSafeSearch": "Güvenli aramayı 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 akış sunucuları", + "upstreamServers": "Üst kaynak sunucuları", "serverAddress": "Sunucu adresi", - "noUpstreamServers": "Üst akış sunucusu yok.", - "willBeUsedGeneralServers": "Genel üst akış sunucuları kullanılacak.", + "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", @@ -283,7 +283,7 @@ "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 girdiler içeriyorsa, AdGuard Home bu istemcilerden gelen talepleri reddedecektir. Bu alan, İzin Verilen İstemciler'de girdi varsa görmezden gelinir.", + "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ı", @@ -293,15 +293,15 @@ "domainNotAdded": "Alan adı eklenemedi", "statusSelected": "Durum seçildi.", "updateLists": "Listeleri güncelle", - "checkHostFiltered": "Ana bilgisayarı kontrol et", + "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": "Ana bilgisayar kontrol ediliyor", - "errorCheckingHost": "Ana bilgisayar kontrol edilemedi", + "checkingHost": "Kontrol ediliyor", + "errorCheckingHost": "Kontrol etme başarısız", "block": "Engelle", "unblock": "Engeli kaldır", "custom": "Özel", @@ -336,7 +336,7 @@ "updating": "Değerler güncelleniyor...", "blockedServicesUpdated": "Engellenen hizmetler başarıyla güncellendi", "blockedServicesNotUpdated": "Engellenen hizmetler güncellenemedi", - "insertDomain": "Durumu kontrol etmek için bir alan adı ekleyin.", + "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", @@ -394,18 +394,18 @@ "noLeases": "Kullanılabilir DHCP kiralaması yok", "dnsRewrites": "DNS yeniden yazımları", "dnsRewritesDescription": "Özel DNS kurallarını yapılandır", - "loadingRewriteRules": "Yeniden yazma kuralları yükleniyor...", - "rewriteRulesNotLoaded": "DNS yeniden yazma kuralları yüklenemedi.", + "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 yazmasını silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", - "dnsRewriteRuleDeleted": "DNS yeniden yazma kuralı başarıyla silindi", - "dnsRewriteRuleNotDeleted": "DNS yeniden yazma kuralı silinemedi", + "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 yazma ekleniyor...", - "dnsRewriteRuleAdded": "DNS yeniden yazma kuralı başarıyla eklendi", - "dnsRewriteRuleNotAdded": "DNS yeniden yazma kuralı eklenemedi", + "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", @@ -422,33 +422,33 @@ "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", + "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 akış DNS sunucuları", + "upstreamDns": "Üst kaynak DNS sunucuları", "bootstrapDns": "Önyükleme DNS sunucuları", - "noUpstreamDns": "Üst akış DNS sunucuları eklenmedi.", + "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 akış 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 akış sunucularını aynı anda sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanın.", + "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 akışlarda belirttiğiniz DoH/DoT çözümleyicilerinin IP adreslerini çözmek için kullanılır.", + "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 akış 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.", + "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 yukarı akış sunucuları) istemcilerin ana bilgisayar adlarını tersine çöz.", + "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", @@ -460,13 +460,13 @@ "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", + "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)", + "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", + "customIpDescription": "Manuel olarak ayarlanmış bir IP adresi ile yanıt verin.", "dnsCacheConfig": "DNS önbellek yapılandırması", "cacheSize": "Önbellek boyutu", "inBytes": "Bayt olarak", @@ -487,7 +487,7 @@ "dnsConfigNotSaved": "DNS sunucusu yapılandırması kaydedilemedi", "savingConfig": "Yapılandırma kaydediliyor...", "someValueNotValid": "Bazı değerler geçerli değil", - "upstreamDnsDescription": "Üst akış sunucularını ve DNS modunu yapılandır", + "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", @@ -551,7 +551,7 @@ "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", + "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", @@ -624,18 +624,18 @@ "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", + "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", + "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 yazma kuralını düzenle", - "dnsRewriteRuleUpdated": "DNS yeniden yazma kuralı başarıyla güncellendi", - "dnsRewriteRuleNotUpdated": "DNS yeniden yazma kuralı güncellenemedi", + "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.", @@ -657,7 +657,7 @@ "quickFilters": "Hızlı filtreler", "searchDomainInternet": "İnternette alan adı ara", "hideServerAddress": "Sunucu adresini gizle", - "hideServerAddressDescription": "Ana ekranda sunucu adresini gizler", + "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.", @@ -682,9 +682,11 @@ "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ı kontrol etme\" seçeneğini etkinleştirdiğinizden emin olun.", + "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}" + "minimumRequiredVersion": "Gerekli minimum sürüm: {version}", + "topUpstreams": "Öne çıkan üst kaynaklar", + "averageUpstreamResponseTime": "Üst kaynak ortalama yanıt süresi" } From f53a498c03a5b8e774f653eb88b6f17c903ec7f9 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 26 Nov 2023 22:42:05 +0100 Subject: [PATCH 125/177] Changed options menus --- lib/screens/clients/added_list.dart | 12 -- .../clients/client/added_client_tile.dart | 2 - lib/screens/clients/clients_list.dart | 4 +- lib/screens/clients/options_modal.dart | 72 ------- lib/screens/clients/search_clients.dart | 183 ++++++++++-------- .../filters/filters_triple_column.dart | 45 ++--- lib/screens/filters/list_options_menu.dart | 72 +++---- lib/widgets/custom_list_tile_dialog.dart | 4 +- lib/widgets/options_menu.dart | 67 ++++++- lib/widgets/options_modal.dart | 67 ------- 10 files changed, 225 insertions(+), 303 deletions(-) delete mode 100644 lib/screens/clients/options_modal.dart delete mode 100644 lib/widgets/options_modal.dart diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index 124f32c..a272940 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -11,7 +11,6 @@ import 'package:adguard_home_manager/screens/clients/client/client_screen_functi 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'; @@ -144,16 +143,6 @@ class _AddedListState extends State { ); } - void openOptionsModal(Client client) { - showModal( - context: context, - builder: (ctx) => OptionsModal( - onDelete: () => openDeleteModal(client), - onEdit: () => openClientModal(client), - ) - ); - } - return CustomTabContentList( listPadding: widget.splitView == true ? const EdgeInsets.only(top: 8) @@ -182,7 +171,6 @@ class _AddedListState extends State { selectedClient: widget.selectedClient, client: widget.data[index], onTap: widget.onClientSelected, - onLongPress: openOptionsModal, onEdit: statusProvider.serverStatus != null ? (c) => openClientModal(c) : null, diff --git a/lib/screens/clients/client/added_client_tile.dart b/lib/screens/clients/client/added_client_tile.dart index a33578f..4f4037e 100644 --- a/lib/screens/clients/client/added_client_tile.dart +++ b/lib/screens/clients/client/added_client_tile.dart @@ -13,7 +13,6 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart'; 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) onDelete; final Client? selectedClient; @@ -23,7 +22,6 @@ class AddedClientTile extends StatefulWidget { super.key, required this.client, required this.onTap, - required this.onLongPress, this.onEdit, required this.onDelete, this.selectedClient, diff --git a/lib/screens/clients/clients_list.dart b/lib/screens/clients/clients_list.dart index 2e52738..bdff4ee 100644 --- a/lib/screens/clients/clients_list.dart +++ b/lib/screens/clients/clients_list.dart @@ -16,12 +16,12 @@ class ClientsList extends StatelessWidget { final bool splitView; const ClientsList({ - Key? key, + super.key, required this.data, required this.onClientSelected, this.selectedClient, required this.splitView, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/screens/clients/options_modal.dart b/lib/screens/clients/options_modal.dart deleted file mode 100644 index d779aae..0000000 --- a/lib/screens/clients/options_modal.dart +++ /dev/null @@ -1,72 +0,0 @@ -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/status_provider.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) { - final statusProvider = Provider.of(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), - if (statusProvider.serverStatus != null) 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) - ) - ], - ); - } -} \ No newline at end of file diff --git a/lib/screens/clients/search_clients.dart b/lib/screens/clients/search_clients.dart index 895f458..d8e1104 100644 --- a/lib/screens/clients/search_clients.dart +++ b/lib/screens/clients/search_clients.dart @@ -7,11 +7,12 @@ import 'package:flutter_gen/gen_l10n/app_localizations.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/screens/clients/options_modal.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/snackbar.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; @@ -151,15 +152,15 @@ class _SearchClientsState extends State { ); } - 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( @@ -224,82 +225,96 @@ class _SearchClientsState extends State { 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: 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 + itemBuilder: (context, index) => OptionsMenu( + options: [ + MenuOption( + icon: Icons.edit_rounded, + title: AppLocalizations.of(context)!.edit, + action: (v) => openClientModal(v) + ), + MenuOption( + icon: Icons.delete_rounded, + title: AppLocalizations.of(context)!.delete, + action: (v) => 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: 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 - ) - ], - ) - ], + 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 + ) + ], + ) + ], + ), ), ) ) diff --git a/lib/screens/filters/filters_triple_column.dart b/lib/screens/filters/filters_triple_column.dart index 2042068..3401bac 100644 --- a/lib/screens/filters/filters_triple_column.dart +++ b/lib/screens/filters/filters_triple_column.dart @@ -7,10 +7,10 @@ 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'; @@ -28,11 +28,11 @@ class FiltersTripleColumn extends StatelessWidget { final List actions; const FiltersTripleColumn({ - Key? key, + super.key, required this.onRemoveCustomRule, required this.onOpenDetailsModal, required this.actions, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -282,28 +282,25 @@ class FiltersTripleColumn extends StatelessWidget { } ), ], - 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, - ) - ) - ] + 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, + ) ) - ), - 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, + ], + 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, + ), ), ), ), diff --git a/lib/screens/filters/list_options_menu.dart b/lib/screens/filters/list_options_menu.dart index f4337a1..4cb6865 100644 --- a/lib/screens/filters/list_options_menu.dart +++ b/lib/screens/filters/list_options_menu.dart @@ -8,7 +8,7 @@ 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'; @@ -144,41 +144,41 @@ class ListOptionsMenu extends StatelessWidget { child: Material( color: Colors.transparent, child: InkWell( - onLongPress: Platform.isAndroid || Platform.isIOS ? () => 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 - ) - ), - 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: (_) => openSelectionMode() - ), - ] - ) - ) : null, - 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 + ) ), ), ); diff --git a/lib/widgets/custom_list_tile_dialog.dart b/lib/widgets/custom_list_tile_dialog.dart index 1d09760..2e3cff4 100644 --- a/lib/widgets/custom_list_tile_dialog.dart +++ b/lib/widgets/custom_list_tile_dialog.dart @@ -6,11 +6,11 @@ class CustomListTileDialog extends StatelessWidget { final void Function()? onTap; const CustomListTileDialog({ - Key? key, + super.key, required this.title, this.icon, this.onTap - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/widgets/options_menu.dart b/lib/widgets/options_menu.dart index 34b755b..b6f6ec7 100644 --- a/lib/widgets/options_menu.dart +++ b/lib/widgets/options_menu.dart @@ -2,8 +2,9 @@ import 'dart:io'; import 'package:contextmenu/contextmenu.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/widgets/options_modal.dart'; +import 'package:adguard_home_manager/widgets/custom_list_tile_dialog.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/models/menu_option.dart'; @@ -29,7 +30,7 @@ class OptionsMenu extends StatelessWidget { void openOptionsModal() { showDialog( context: context, - builder: (context) => OptionsModal( + builder: (context) => _OptionsModal( options: options, value: value ) @@ -60,4 +61,66 @@ class OptionsMenu extends StatelessWidget { ), ); } +} + +class _OptionsModal extends StatelessWidget { + final List options; + final dynamic value; + + const _OptionsModal({ + super.key, + required this.options, + this.value, + }); + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: const EdgeInsets.symmetric(vertical: 16), + title: Column( + children: [ + Icon( + Icons.more_horiz, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.options, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + content: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400 + ), + child: SingleChildScrollView( + child: Wrap( + children: options.map((opt) => CustomListTileDialog( + title: opt.title, + icon: opt.icon, + onTap: () { + Navigator.pop(context); + opt.action(value); + }, + )).toList() + ), + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ) + ], + ) + ], + ); + } } \ No newline at end of file diff --git a/lib/widgets/options_modal.dart b/lib/widgets/options_modal.dart deleted file mode 100644 index 102f7ca..0000000 --- a/lib/widgets/options_modal.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:adguard_home_manager/models/menu_option.dart'; -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 List options; - final dynamic value; - - const OptionsModal({ - super.key, - required this.options, - this.value, - }); - - @override - Widget build(BuildContext context) { - return AlertDialog( - contentPadding: const EdgeInsets.symmetric(vertical: 16), - title: Column( - children: [ - Icon( - Icons.more_horiz, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.options, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ) - ], - ), - content: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 400 - ), - child: SingleChildScrollView( - child: Wrap( - children: options.map((opt) => CustomListTileDialog( - title: opt.title, - icon: opt.icon, - onTap: () { - Navigator.pop(context); - opt.action(value); - }, - )).toList() - ), - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) - ) - ], - ) - ], - ); - } -} \ No newline at end of file From 69ee579139c4603cc5e820bebf32bc98f4f97dec Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 26 Nov 2023 22:51:51 +0100 Subject: [PATCH 126/177] Fixed clients search --- lib/screens/clients/added_list.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index a272940..0a0bc08 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -142,6 +142,11 @@ class _AddedListState extends State { ) ); } + 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( listPadding: widget.splitView == true @@ -166,10 +171,10 @@ class _AddedListState extends State { ], ), ), - itemsCount: widget.data.length, + itemsCount: clientsDisplay.length, contentWidget: (index) => AddedClientTile( selectedClient: widget.selectedClient, - client: widget.data[index], + client: clientsDisplay[index], onTap: widget.onClientSelected, onEdit: statusProvider.serverStatus != null ? (c) => openClientModal(c) From 991a73c47a4188629778d6562ea8fbd72db90dbd Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 26 Nov 2023 22:53:08 +0100 Subject: [PATCH 127/177] Improvement --- lib/widgets/options_menu.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/widgets/options_menu.dart b/lib/widgets/options_menu.dart index b6f6ec7..861edab 100644 --- a/lib/widgets/options_menu.dart +++ b/lib/widgets/options_menu.dart @@ -68,7 +68,6 @@ class _OptionsModal extends StatelessWidget { final dynamic value; const _OptionsModal({ - super.key, required this.options, this.value, }); From 1c009df41f2667306d8489673b7e5854ac63d61d Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 26 Nov 2023 22:54:29 +0100 Subject: [PATCH 128/177] Updated app version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 8770025..0043bc7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.12.0-beta.2+110 +version: 2.12.0+111 environment: sdk: '>=2.18.1 <3.0.0' From 48794b26a81c02f9fa734742d82cabf17dfbb633 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 27 Nov 2023 01:04:23 +0100 Subject: [PATCH 129/177] Fixed copy log domain clipboard --- lib/screens/logs/log_tile.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/screens/logs/log_tile.dart b/lib/screens/logs/log_tile.dart index 95981e8..af130e2 100644 --- a/lib/screens/logs/log_tile.dart +++ b/lib/screens/logs/log_tile.dart @@ -319,10 +319,13 @@ class LogTile extends StatelessWidget { newStatus: domainBlocked == true ? 'unblock' : 'block' ) ), - MenuOption( + if (log.question.name != null) MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: (v) => copyToClipboard(value: v, successMessage: AppLocalizations.of(context)!.copiedClipboard) + action: (_) => copyToClipboard( + value: log.question.name!, + successMessage: AppLocalizations.of(context)!.copiedClipboard + ) ) ], child: Container( From ffe30150a31a43d61e3f7ac16b6559e3cfc92796 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 27 Nov 2023 14:45:22 +0100 Subject: [PATCH 130/177] Add status code sentry --- lib/services/api_client.dart | 78 ++++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 13 deletions(-) diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index 66ac615..25b6fa3 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -166,7 +166,11 @@ class ApiClientV2 { content: clients ); } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + Sentry.captureException( + e, + stackTrace: stackTrace, + hint: Hint.withMap({ "statusCode": results.map((e) => e.statusCode.toString()) }) + ); return const ApiResponse(successful: false); } } @@ -210,7 +214,11 @@ class ApiClientV2 { content: LogsData.fromJson(jsonDecode(result.body!)) ); } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + Sentry.captureException( + e, + stackTrace: stackTrace, + hint: Hint.withMap({ "statusCode": result.statusCode.toString() }) + ); return const ApiResponse(successful: false); } } @@ -228,7 +236,11 @@ class ApiClientV2 { content: FilteringStatus.fromJson(jsonDecode(result.body!)) ); } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + Sentry.captureException( + e, + stackTrace: stackTrace, + hint: Hint.withMap({ "statusCode": result.statusCode.toString() }) + ); return const ApiResponse(successful: false); } } @@ -298,7 +310,11 @@ class ApiClientV2 { }) ); } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + Sentry.captureException( + e, + stackTrace: stackTrace, + hint: Hint.withMap({ "statusCode": results.map((e) => e.statusCode.toString()) }) + ); return const ApiResponse(successful: false); } } @@ -364,7 +380,11 @@ class ApiClientV2 { content: ServerInfoData.fromJson(jsonDecode(result.body!)) ); } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + Sentry.captureException( + e, + stackTrace: stackTrace, + hint: Hint.withMap({ "statusCode": result.statusCode.toString() }) + ); return const ApiResponse(successful: false); } } @@ -398,7 +418,11 @@ class ApiClientV2 { content: {'updated': jsonDecode(results[0].body!)['updated']+jsonDecode(results[1].body!)['updated']} ); } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + Sentry.captureException( + e, + stackTrace: stackTrace, + hint: Hint.withMap({ "statusCode": results.map((e) => e.statusCode.toString()) }) + ); return const ApiResponse(successful: false); } } @@ -463,7 +487,11 @@ class ApiClientV2 { ) ); } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + Sentry.captureException( + e, + stackTrace: stackTrace, + hint: Hint.withMap({ "statusCode": results.map((e) => e.statusCode.toString()) }) + ); return const ApiResponse(successful: false); } } @@ -549,7 +577,11 @@ class ApiClientV2 { content: data ); } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + Sentry.captureException( + e, + stackTrace: stackTrace, + hint: Hint.withMap({ "statusCode": result.statusCode.toString() }) + ); return const ApiResponse(successful: false); } } @@ -622,7 +654,11 @@ class ApiClientV2 { content: DnsInfo.fromJson(jsonDecode(result.body!)) ); } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + Sentry.captureException( + e, + stackTrace: stackTrace, + hint: Hint.withMap({ "statusCode": result.statusCode.toString() }) + ); return const ApiResponse(successful: false); } } @@ -658,7 +694,11 @@ class ApiClientV2 { content: EncryptionData.fromJson(jsonDecode(result.body!)) ); } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + Sentry.captureException( + e, + stackTrace: stackTrace, + hint: Hint.withMap({ "statusCode": result.statusCode.toString() }) + ); return const ApiResponse(successful: false); } } @@ -678,7 +718,11 @@ class ApiClientV2 { ) ); } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + Sentry.captureException( + e, + stackTrace: stackTrace, + hint: Hint.withMap({ "statusCode": result.statusCode.toString() }) + ); return const ApiResponse(successful: false); } } @@ -712,7 +756,11 @@ class ApiClientV2 { ) : null ); } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + Sentry.captureException( + e, + stackTrace: stackTrace, + hint: Hint.withMap({ "statusCode": result.statusCode.toString() }) + ); return const ApiResponse(successful: false); } } @@ -758,7 +806,11 @@ class ApiClientV2 { content: obj ); } catch (e, stackTrace) { - Sentry.captureException(e, stackTrace: stackTrace); + Sentry.captureException( + e, + stackTrace: stackTrace, + hint: Hint.withMap({ "statusCode": results.map((e) => e.statusCode.toString()) }) + ); return const ApiResponse(successful: false); } } From 06d543553f880adcf13bb85fab65ee8689431331 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 27 Nov 2023 14:45:54 +0100 Subject: [PATCH 131/177] Updated app version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 0043bc7..bfddb2f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.12.0+111 +version: 2.12.1+112 environment: sdk: '>=2.18.1 <3.0.0' From 946f779567ef57f4b70f8d3504a01758b7d1f4c5 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 27 Nov 2023 15:05:26 +0100 Subject: [PATCH 132/177] Fixed encryption error message --- lib/services/api_client.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index 25b6fa3..636cc34 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -743,7 +743,7 @@ class ApiClientV2 { return ApiResponse( successful: result.successful, content: result.body != null ? EncryptionValidationResult( - isObject: false, + isObject: true, encryptionValidation: EncryptionValidation.fromJson(jsonDecode(result.body!)) ) : null ); From bddfac7e721b13f701943fc241a28b7b82a164a9 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 29 Nov 2023 10:32:12 +0100 Subject: [PATCH 133/177] Removed fixed text size --- lib/main.dart | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 5b38138..9734263 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -236,20 +236,9 @@ class _MainState extends State
{ ], scaffoldMessengerKey: scaffoldMessengerKey, navigatorKey: globalNavigatorKey, - builder: (context, child) { - return CustomMenuBar( - child: MediaQuery( - data: MediaQuery.of(context).copyWith( - textScaler: TextScaler.linear( - !(Platform.isAndroid || Platform.isIOS) - ? 0.9 - : 1.0 - ) - ), - child: child!, - ), - ); - }, + builder: (context, child) => CustomMenuBar( + child: child!, + ), home: const Layout(), ), ); From c9c960d1ee38f52667aa533ade8d76cbc7e6e106 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 29 Nov 2023 11:56:28 +0100 Subject: [PATCH 134/177] Adapt ui to support text scale --- .../client/blocked_services_section.dart | 60 +-- lib/screens/clients/client/client_form.dart | 16 +- lib/screens/clients/client/client_screen.dart | 4 +- lib/screens/clients/client/tags_modal.dart | 18 +- .../modals/update_interval_lists_modal.dart | 340 +++++++++-------- .../filters/selection/selection_screen.dart | 11 +- lib/screens/home/chart.dart | 4 +- .../home/management_modal/main_switch.dart | 64 ++-- .../management_modal/management_modal.dart | 3 +- lib/screens/home/server_status.dart | 99 +++-- lib/screens/home/status_box.dart | 4 +- .../logs/configuration/config_widgets.dart | 34 +- .../logs/configuration/logs_config_modal.dart | 1 - lib/screens/logs/filters/clients_modal.dart | 10 +- .../logs/filters/filter_status_modal.dart | 354 ++++++++++-------- .../settings/dhcp/add_static_lease_modal.dart | 42 ++- lib/screens/settings/dhcp/dhcp.dart | 1 + .../settings/dhcp/dhcp_interface_item.dart | 17 +- .../settings/dhcp/select_interface_modal.dart | 4 +- .../settings/dns/clear_dns_cache_dialog.dart | 5 +- .../settings/dns_rewrites/dns_rewrites.dart | 4 +- .../settings/safe_search_settings.dart | 10 +- lib/widgets/custom_list_tile_dialog.dart | 14 +- 23 files changed, 624 insertions(+), 495 deletions(-) diff --git a/lib/screens/clients/client/blocked_services_section.dart b/lib/screens/clients/client/blocked_services_section.dart index 37c01d3..f6effd4 100644 --- a/lib/screens/clients/client/blocked_services_section.dart +++ b/lib/screens/clients/client/blocked_services_section.dart @@ -10,12 +10,12 @@ class BlockedServicesSection extends StatelessWidget { final void Function(bool) onUpdateServicesGlobalSettings; const BlockedServicesSection({ - Key? key, + super.key, required this.useGlobalSettingsServices, required this.blockedServices, required this.onUpdatedBlockedServices, required this.onUpdateServicesGlobalSettings - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -37,11 +37,13 @@ class BlockedServicesSection extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - AppLocalizations.of(context)!.useGlobalSettings, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface + Flexible( + child: Text( + AppLocalizations.of(context)!.useGlobalSettings, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), ), ), Switch( @@ -78,30 +80,32 @@ class BlockedServicesSection extends StatelessWidget { : 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), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Text( - blockedServices.isNotEmpty - ? "${blockedServices.length} ${AppLocalizations.of(context)!.servicesBlocked}" - : AppLocalizations.of(context)!.noBlockedServicesSelected, + AppLocalizations.of(context)!.selectBlockedServices, style: TextStyle( - color: Theme.of(context).listTileTheme.iconColor + 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 + ), + ) + ] + ], + ), ) ], ), diff --git a/lib/screens/clients/client/client_form.dart b/lib/screens/clients/client/client_form.dart index 5d54b86..2151288 100644 --- a/lib/screens/clients/client/client_form.dart +++ b/lib/screens/clients/client/client_form.dart @@ -47,7 +47,7 @@ class ClientForm extends StatelessWidget { final void Function(bool) updateUseGlobalSettingsServices; const ClientForm({ - Key? key, + super.key, required this.isFullScreen, required this.client, required this.nameController, @@ -75,7 +75,7 @@ class ClientForm extends StatelessWidget { required this.updateEnableSafeSearch, required this.updateSafeSearch, required this.updateUseGlobalSettingsServices, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -154,11 +154,13 @@ class ClientForm extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - AppLocalizations.of(context)!.useGlobalSettings, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface + Flexible( + child: Text( + AppLocalizations.of(context)!.useGlobalSettings, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), ), ), Switch( diff --git a/lib/screens/clients/client/client_screen.dart b/lib/screens/clients/client/client_screen.dart index 34fb88b..2ab5c75 100644 --- a/lib/screens/clients/client/client_screen.dart +++ b/lib/screens/clients/client/client_screen.dart @@ -27,12 +27,12 @@ class ClientScreen extends StatefulWidget { final bool fullScreen; const ClientScreen({ - Key? key, + super.key, this.client, required this.onConfirm, this.onDelete, required this.fullScreen - }) : super(key: key); + }); @override State createState() => _ClientScreenState(); diff --git a/lib/screens/clients/client/tags_modal.dart b/lib/screens/clients/client/tags_modal.dart index 42843b1..0756c83 100644 --- a/lib/screens/clients/client/tags_modal.dart +++ b/lib/screens/clients/client/tags_modal.dart @@ -7,11 +7,11 @@ class TagsModal extends StatefulWidget { final void Function(List) onConfirm; const TagsModal({ - Key? key, + super.key, required this.selectedTags, required this.tags, required this.onConfirm, - }) : super(key: key); + }); @override State createState() => _TagsModalState(); @@ -82,12 +82,14 @@ class _TagsModalState extends State { 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( diff --git a/lib/screens/filters/modals/update_interval_lists_modal.dart b/lib/screens/filters/modals/update_interval_lists_modal.dart index deb714f..4827082 100644 --- a/lib/screens/filters/modals/update_interval_lists_modal.dart +++ b/lib/screens/filters/modals/update_interval_lists_modal.dart @@ -12,11 +12,11 @@ class UpdateIntervalListsModal extends StatefulWidget { final bool dialog; const UpdateIntervalListsModal({ - Key? key, + super.key, required this.interval, required this.onChange, required this.dialog - }) : super(key: key); + }); @override State createState() => _UpdateIntervalListsModalState(); @@ -25,7 +25,7 @@ class UpdateIntervalListsModal extends StatefulWidget { class _UpdateIntervalListsModalState extends State { int? selectedOption; - void _updateRadioValue(value) { + void _updateRadioValue(int value) { setState(() { selectedOption = value; }); @@ -41,18 +41,67 @@ class _UpdateIntervalListsModalState extends State { 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( + 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: _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), @@ -62,7 +111,7 @@ class _UpdateIntervalListsModalState extends State { color: Theme.of(context).listTileTheme.iconColor ), ), - Container( + Padding( padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 16 @@ -78,157 +127,128 @@ class _UpdateIntervalListsModalState extends State { ), ), ], - ) + ), + ) + ], + ), + 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.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, - label: AppLocalizations.of(context)!.never, - ), - ), - ), - FractionallySizedBox( - widthFactor: 0.5, - child: Padding( - padding: const EdgeInsets.only(left: 6), - child: OptionBox( - optionsValue: selectedOption, - itemValue: 1, - onTap: _updateRadioValue, - label: AppLocalizations.of(context)!.hour1, - ), - ), - ), - FractionallySizedBox( - widthFactor: 0.5, - child: Padding( - padding: const EdgeInsets.only(right: 6), - child: OptionBox( - optionsValue: selectedOption, - itemValue: 12, - onTap: _updateRadioValue, - label: AppLocalizations.of(context)!.hours12, - ), - ), - ), - FractionallySizedBox( - widthFactor: 0.5, - child: Padding( - padding: const EdgeInsets.only(left: 6), - child: OptionBox( - optionsValue: selectedOption, - itemValue: 24, - onTap: _updateRadioValue, - label: AppLocalizations.of(context)!.hours24, - - ), - ), - ), - FractionallySizedBox( - widthFactor: 0.5, - child: Padding( - padding: const EdgeInsets.only(right: 6), - child: OptionBox( - optionsValue: selectedOption, - itemValue: 72, - onTap: _updateRadioValue, - label: AppLocalizations.of(context)!.days3, - - ), - ), - ), - FractionallySizedBox( - widthFactor: 0.5, - child: Padding( - padding: const EdgeInsets.only(left: 6), - child: OptionBox( - optionsValue: selectedOption, - itemValue: 168, - onTap: _updateRadioValue, - 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); - 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) - ), + 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), + ), + ], ), - child: content() ), - ); - } + if (Platform.isIOS) const SizedBox(height: 16) + ], + ); } } \ No newline at end of file diff --git a/lib/screens/filters/selection/selection_screen.dart b/lib/screens/filters/selection/selection_screen.dart index 11539b2..1475454 100644 --- a/lib/screens/filters/selection/selection_screen.dart +++ b/lib/screens/filters/selection/selection_screen.dart @@ -197,7 +197,7 @@ class _SelectionScreenState extends State with TickerProviderSt ), _Tab( icon: Icons.gpp_bad_rounded, - text: AppLocalizations.of(context)!.blacklist, + text: AppLocalizations.of(context)!.blacklists, quantity: _selectedBlacklists.length ), ] @@ -263,7 +263,7 @@ class _SelectionScreenState extends State with TickerProviderSt ), _Tab( icon: Icons.gpp_bad_rounded, - text: AppLocalizations.of(context)!.blacklist, + text: AppLocalizations.of(context)!.blacklists, quantity: _selectedBlacklists.length ), ] @@ -336,7 +336,12 @@ class _Tab extends StatelessWidget { children: [ Icon(icon), const SizedBox(width: 8), - Text(text), + Flexible( + child: Text( + text, + overflow: TextOverflow.ellipsis, + ) + ), const SizedBox(width: 8), Container( height: 22, diff --git a/lib/screens/home/chart.dart b/lib/screens/home/chart.dart index 682d5b9..ba97f79 100644 --- a/lib/screens/home/chart.dart +++ b/lib/screens/home/chart.dart @@ -14,14 +14,14 @@ class HomeChart extends StatelessWidget { final int hoursInterval; 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); + }); @override Widget build(BuildContext context) { diff --git a/lib/screens/home/management_modal/main_switch.dart b/lib/screens/home/management_modal/main_switch.dart index d4ce5ed..3dbe0ec 100644 --- a/lib/screens/home/management_modal/main_switch.dart +++ b/lib/screens/home/management_modal/main_switch.dart @@ -15,12 +15,12 @@ class MainSwitch extends StatelessWidget { final Animation animation; const MainSwitch({ - Key? key, + super.key, required this.expandableController, required this.updateBlocking, required this.disableWithCountdown, required this.animation, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -88,12 +88,11 @@ class _TopRow extends StatelessWidget { final Animation animation; const _TopRow({ - Key? key, required this.legacyMode, required this.expandableController, required this.updateBlocking, required this.animation, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -102,9 +101,9 @@ class _TopRow extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - if (legacyMode == false) ...[ + Expanded( + child: Row( + children: [ RotationTransition( turns: animation, child: Icon( @@ -116,26 +115,30 @@ class _TopRow extends StatelessWidget { ), ), const SizedBox(width: 8), - ], - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.allProtections, - style: const TextStyle( - fontSize: 18, - ), + 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)}" + ) + ] + ], ), - 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 @@ -145,7 +148,7 @@ class _TopRow extends StatelessWidget { } updateBlocking( value: value, - filter: legacyMode == true ? 'general_legacy' : 'general' + filter: 'general' ); } : null, ) @@ -158,9 +161,8 @@ class _BottomRow extends StatefulWidget { final void Function(int) disableWithCountdown; const _BottomRow({ - Key? key, required this.disableWithCountdown, - }) : super(key: key); + }); @override State<_BottomRow> createState() => _BottomRowState(); @@ -173,8 +175,12 @@ class _BottomRowState extends State<_BottomRow> { Widget build(BuildContext context) { final statusProvider = Provider.of(context); + final textScale = MediaQuery.of(context).textScaleFactor; + return Container( - height: Platform.isMacOS || Platform.isLinux || Platform.isWindows ? 50 : 40, + height: Platform.isMacOS || Platform.isLinux || Platform.isWindows + ? 50 * textScale + : 40 * textScale, margin: const EdgeInsets.only(top: 8), child: Scrollbar( controller: _chipsScrollController, diff --git a/lib/screens/home/management_modal/management_modal.dart b/lib/screens/home/management_modal/management_modal.dart index 5096a9d..79fe043 100644 --- a/lib/screens/home/management_modal/management_modal.dart +++ b/lib/screens/home/management_modal/management_modal.dart @@ -181,12 +181,11 @@ class _Modal extends StatelessWidget { final Animation animation; const _Modal({ - Key? key, required this.expandableController, required this.updateBlocking, required this.disableWithCountdown, required this.animation, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/screens/home/server_status.dart b/lib/screens/home/server_status.dart index c130564..a0e1715 100644 --- a/lib/screens/home/server_status.dart +++ b/lib/screens/home/server_status.dart @@ -9,17 +9,43 @@ 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).textScaleFactor; + + double boxSize() { + if (textScaleFactor < 1 || (textScaleFactor >= 1 && textScaleFactor < 1.15)) { + return 65; + } + else if (textScaleFactor >= 1.15 && textScaleFactor < 1.3) { + return 75; + } + else if (textScaleFactor >= 1.3 && textScaleFactor < 1.45) { + return 80; + } + else if (textScaleFactor >= 1.45 && textScaleFactor < 1.6) { + return 85; + } + else if (textScaleFactor >= 1.6 && textScaleFactor < 1.85) { + return 100; + } + else if (textScaleFactor >= 1.85) { + return 110; + } + else { + return 65; + } + } + + 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 +56,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: boxSize() ), + 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 + ), + ], ) ], ), diff --git a/lib/screens/home/status_box.dart b/lib/screens/home/status_box.dart index 2f57106..38f6c95 100644 --- a/lib/screens/home/status_box.dart +++ b/lib/screens/home/status_box.dart @@ -9,11 +9,11 @@ class StatusBox extends StatelessWidget { final bool isEnabled; const StatusBox({ - Key? key, + super.key, required this.icon, required this.label, required this.isEnabled - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/screens/logs/configuration/config_widgets.dart b/lib/screens/logs/configuration/config_widgets.dart index 2d70285..1fa59f9 100644 --- a/lib/screens/logs/configuration/config_widgets.dart +++ b/lib/screens/logs/configuration/config_widgets.dart @@ -17,7 +17,7 @@ class LogsConfigOptions extends StatelessWidget { final void Function() onConfirm; const LogsConfigOptions({ - Key? key, + super.key, required this.generalSwitch, required this.updateGeneralSwitch, required this.anonymizeClientIp, @@ -27,10 +27,12 @@ class LogsConfigOptions extends StatelessWidget { required this.updateRetentionTime, required this.onClear, required this.onConfirm - }) : super(key: key); + }); @override Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -111,10 +113,12 @@ class LogsConfigOptions extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - AppLocalizations.of(context)!.anonymizeClientIp, - style: const TextStyle( - fontSize: 16 + Flexible( + child: Text( + AppLocalizations.of(context)!.anonymizeClientIp, + style: const TextStyle( + fontSize: 16 + ), ), ), Switch( @@ -159,13 +163,21 @@ class LogsConfigOptions extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - TextButton( + if (width > 500) TextButton( onPressed: () { Navigator.pop(context); onClear(); }, child: Text(AppLocalizations.of(context)!.clearLogs) ), + if (width <= 500) IconButton( + onPressed: () { + Navigator.pop(context); + onClear(); + }, + icon: const Icon(Icons.delete_rounded), + tooltip: AppLocalizations.of(context)!.clearLogs, + ), Row( children: [ TextButton( @@ -174,7 +186,7 @@ class LogsConfigOptions extends StatelessWidget { ), const SizedBox(width: 20), TextButton( - onPressed: retentionTime != '' + onPressed: retentionTime != null ? () { Navigator.pop(context); onConfirm(); @@ -183,7 +195,7 @@ class LogsConfigOptions extends StatelessWidget { child: Text( AppLocalizations.of(context)!.confirm, style: TextStyle( - color: retentionTime != '' + color: retentionTime != null ? Theme.of(context).colorScheme.primary : Colors.grey ), @@ -201,7 +213,7 @@ class LogsConfigOptions extends StatelessWidget { } class ConfigLogsLoading extends StatelessWidget { - const ConfigLogsLoading({Key? key}) : super(key: key); + const ConfigLogsLoading({super.key}); @override Widget build(BuildContext context) { @@ -232,7 +244,7 @@ class ConfigLogsLoading extends StatelessWidget { } class ConfigLogsError extends StatelessWidget { - const ConfigLogsError({Key? key}) : super(key: key); + const ConfigLogsError({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/screens/logs/configuration/logs_config_modal.dart b/lib/screens/logs/configuration/logs_config_modal.dart index fb260b9..1b6c641 100644 --- a/lib/screens/logs/configuration/logs_config_modal.dart +++ b/lib/screens/logs/configuration/logs_config_modal.dart @@ -143,7 +143,6 @@ class _LogsConfigModalState extends State { } else { return Container( - height: Platform.isIOS ? 436 : 420, decoration: BoxDecoration( borderRadius: const BorderRadius.only( topLeft: Radius.circular(28), diff --git a/lib/screens/logs/filters/clients_modal.dart b/lib/screens/logs/filters/clients_modal.dart index 7bb0574..1730d8e 100644 --- a/lib/screens/logs/filters/clients_modal.dart +++ b/lib/screens/logs/filters/clients_modal.dart @@ -12,10 +12,10 @@ class ClientsModal extends StatefulWidget { final bool dialog; const ClientsModal({ - Key? key, + super.key, required this.value, required this.dialog - }) : super(key: key); + }); @override State createState() => _ClientsModalState(); @@ -75,10 +75,9 @@ class _ModalContent extends StatelessWidget { final void Function(List) onClientsSelected; const _ModalContent({ - Key? key, required this.selectedClients, required this.onClientsSelected, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -188,11 +187,10 @@ class _ListItem extends StatelessWidget { final void Function(bool) onChanged; const _ListItem({ - Key? key, required this.label, required this.checkboxActive, required this.onChanged, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/screens/logs/filters/filter_status_modal.dart b/lib/screens/logs/filters/filter_status_modal.dart index aec5aac..d3c1079 100644 --- a/lib/screens/logs/filters/filter_status_modal.dart +++ b/lib/screens/logs/filters/filter_status_modal.dart @@ -11,10 +11,10 @@ class FilterStatusModal extends StatefulWidget { final bool dialog; const FilterStatusModal({ - Key? key, + super.key, required this.value, required this.dialog - }) : super(key: key); + }); @override State createState() => _FilterStatusModalState(); @@ -39,164 +39,17 @@ class _FilterStatusModalState extends State { Navigator.pop(context); } - Widget filterStatusListItem({ - required String id, - required IconData icon, - required String label, - required void Function(String?) onChanged - }) { - return Material( - color: Colors.transparent, - child: InkWell( - onTap: () => onChanged(id), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24, 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, - color: Theme.of(context).colorScheme.onSurface - ), - ) - ], - ), - Radio( - value: id, - groupValue: selectedResultStatus, - onChanged: onChanged - ) - ], - ), - ), - ), - ); - } - - 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, - bottom: 16, - ), - child: Icon( - Icons.shield_rounded, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - Text( - AppLocalizations.of(context)!.responseStatus, - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ) - ], - ), - Container(height: 16), - filterStatusListItem( - id: "all", - icon: Icons.shield_rounded, - label: AppLocalizations.of(context)!.all, - onChanged: (value) => setState(() => selectedResultStatus = value!) - ), - filterStatusListItem( - id: "filtered", - icon: Icons.shield_rounded, - label: AppLocalizations.of(context)!.filtered, - onChanged: (value) => setState(() => selectedResultStatus = value!) - ), - filterStatusListItem( - id: "processed", - icon: Icons.verified_user_rounded, - label: AppLocalizations.of(context)!.processedRow, - onChanged: (value) => setState(() => selectedResultStatus = value!) - ), - filterStatusListItem( - id: "whitelisted", - icon: Icons.verified_user_rounded, - label: AppLocalizations.of(context)!.processedWhitelistRow, - onChanged: (value) => setState(() => selectedResultStatus = value!) - ), - filterStatusListItem( - id: "blocked", - icon: Icons.gpp_bad_rounded, - label: AppLocalizations.of(context)!.blocked, - onChanged: (value) => setState(() => selectedResultStatus = value!) - ), - filterStatusListItem( - id: "blocked_safebrowsing", - icon: Icons.gpp_bad_rounded, - label: AppLocalizations.of(context)!.blockedSafeBrowsingRow, - onChanged: (value) => setState(() => selectedResultStatus = value!) - ), - filterStatusListItem( - id: "blocked_parental", - icon: Icons.gpp_bad_rounded, - label: AppLocalizations.of(context)!.blockedParentalRow, - onChanged: (value) => setState(() => selectedResultStatus = value!) - ), - filterStatusListItem( - id: "safe_search", - icon: Icons.gpp_bad_rounded, - label: AppLocalizations.of(context)!.blockedSafeSearchRow, - onChanged: (value) => setState(() => selectedResultStatus = value!) - ), - - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.all(24), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: apply, - child: Text(AppLocalizations.of(context)!.apply) - ) - ], - ), - ), - if (Platform.isIOS) const SizedBox(height: 16) - ], - ); - } - if (widget.dialog == true) { return Dialog( child: ConstrainedBox( constraints: const BoxConstraints( maxWidth: 400 ), - child: content() + child: _Content( + onApply: apply, + updateSelectedResultStatus: (v) => setState(() => selectedResultStatus = v), + selectedResultStatus: selectedResultStatus, + ) ), ); } @@ -209,8 +62,199 @@ class _FilterStatusModalState extends State { ), color: Theme.of(context).dialogBackgroundColor ), - child: content() + child: _Content( + onApply: apply, + updateSelectedResultStatus: (v) => setState(() => selectedResultStatus = v), + selectedResultStatus: selectedResultStatus, + ) ); } } +} + +class _Content extends StatelessWidget { + final String selectedResultStatus; + final void Function(String) updateSelectedResultStatus; + final void Function() onApply; + + const _Content({ + required this.selectedResultStatus, + required this.updateSelectedResultStatus, + required this.onApply, + }); + + @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, + bottom: 16, + ), + child: Icon( + Icons.shield_rounded, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + ), + Text( + AppLocalizations.of(context)!.responseStatus, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ) + ], + ), + Container(height: 16), + _Item( + selectedResultStatus: selectedResultStatus, + id: "all", + icon: Icons.shield_rounded, + label: AppLocalizations.of(context)!.all, + onChanged: updateSelectedResultStatus + ), + _Item( + selectedResultStatus: selectedResultStatus, + id: "filtered", + icon: Icons.shield_rounded, + label: AppLocalizations.of(context)!.filtered, + onChanged: updateSelectedResultStatus + ), + _Item( + selectedResultStatus: selectedResultStatus, + id: "processed", + icon: Icons.verified_user_rounded, + label: AppLocalizations.of(context)!.processedRow, + onChanged: updateSelectedResultStatus + ), + _Item( + selectedResultStatus: selectedResultStatus, + id: "whitelisted", + icon: Icons.verified_user_rounded, + label: AppLocalizations.of(context)!.processedWhitelistRow, + onChanged: updateSelectedResultStatus + ), + _Item( + selectedResultStatus: selectedResultStatus, + id: "blocked", + icon: Icons.gpp_bad_rounded, + label: AppLocalizations.of(context)!.blocked, + onChanged: updateSelectedResultStatus + ), + _Item( + selectedResultStatus: selectedResultStatus, + id: "blocked_safebrowsing", + icon: Icons.gpp_bad_rounded, + label: AppLocalizations.of(context)!.blockedSafeBrowsingRow, + onChanged: updateSelectedResultStatus + ), + _Item( + selectedResultStatus: selectedResultStatus, + id: "blocked_parental", + icon: Icons.gpp_bad_rounded, + label: AppLocalizations.of(context)!.blockedParentalRow, + onChanged: updateSelectedResultStatus + ), + _Item( + selectedResultStatus: selectedResultStatus, + id: "safe_search", + icon: Icons.gpp_bad_rounded, + label: AppLocalizations.of(context)!.blockedSafeSearchRow, + onChanged: updateSelectedResultStatus + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: onApply, + child: Text(AppLocalizations.of(context)!.apply) + ) + ], + ), + ), + if (Platform.isIOS) const SizedBox(height: 16) + ], + ); + } +} + +class _Item extends StatelessWidget { + final String selectedResultStatus; + final String id; + final IconData icon; + final String label; + final void Function(String) onChanged; + + const _Item({ + required this.selectedResultStatus, + required this.id, + required this.icon, + required this.label, + required this.onChanged, + }); + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () => onChanged(id), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Row( + children: [ + Icon( + icon, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + const SizedBox(width: 16), + Flexible( + child: Text( + label, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ) + ], + ), + ), + Radio( + value: id, + groupValue: selectedResultStatus, + onChanged: (v) => onChanged(v!) + ) + ], + ), + ), + ), + ); + } } \ No newline at end of file diff --git a/lib/screens/settings/dhcp/add_static_lease_modal.dart b/lib/screens/settings/dhcp/add_static_lease_modal.dart index a7f2f5f..fd222d3 100644 --- a/lib/screens/settings/dhcp/add_static_lease_modal.dart +++ b/lib/screens/settings/dhcp/add_static_lease_modal.dart @@ -8,10 +8,10 @@ class AddStaticLeaseModal extends StatefulWidget { final bool dialog; const AddStaticLeaseModal({ - Key? key, + super.key, required this.onConfirm, required this.dialog - }) : super(key: key); + }); @override State createState() => _AddStaticLeaseModalState(); @@ -80,26 +80,28 @@ class _AddStaticLeaseModalState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 24), - child: Icon( - Icons.add, - size: 24, - color: Theme.of(context).listTileTheme.iconColor + Flexible( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 24), + child: Icon( + Icons.add, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), ), - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.addStaticLease, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.addStaticLease, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), ), - ), - ], + ], + ), ), ], ), diff --git a/lib/screens/settings/dhcp/dhcp.dart b/lib/screens/settings/dhcp/dhcp.dart index 84a4e76..88f1dad 100644 --- a/lib/screens/settings/dhcp/dhcp.dart +++ b/lib/screens/settings/dhcp/dhcp.dart @@ -414,6 +414,7 @@ class _DhcpScreenState extends State { const SizedBox(height: 30), Text( AppLocalizations.of(context)!.loadingDhcp, + textAlign: TextAlign.center, style: TextStyle( fontSize: 22, color: Theme.of(context).colorScheme.onSurfaceVariant, diff --git a/lib/screens/settings/dhcp/dhcp_interface_item.dart b/lib/screens/settings/dhcp/dhcp_interface_item.dart index 900328e..9d68f4e 100644 --- a/lib/screens/settings/dhcp/dhcp_interface_item.dart +++ b/lib/screens/settings/dhcp/dhcp_interface_item.dart @@ -8,10 +8,10 @@ class DhcpInterfaceItem extends StatelessWidget { final void Function(NetworkInterface) onSelect; const DhcpInterfaceItem({ - Key? key, + super.key, required this.networkInterface, required this.onSelect - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -56,6 +56,7 @@ class DhcpInterfaceItem extends StatelessWidget { const SizedBox(height: 5), if (networkInterface.flags.isNotEmpty) ...[ Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Flags: ", @@ -64,11 +65,13 @@ class DhcpInterfaceItem extends StatelessWidget { color: Theme.of(context).colorScheme.onSurfaceVariant ), ), - Text( - networkInterface.flags.join(', '), - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant + Flexible( + child: Text( + networkInterface.flags.join(', '), + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), ), ), ], diff --git a/lib/screens/settings/dhcp/select_interface_modal.dart b/lib/screens/settings/dhcp/select_interface_modal.dart index 0a740fd..e2dc26d 100644 --- a/lib/screens/settings/dhcp/select_interface_modal.dart +++ b/lib/screens/settings/dhcp/select_interface_modal.dart @@ -13,11 +13,11 @@ class SelectInterfaceModal extends StatelessWidget { final bool dialog; const SelectInterfaceModal({ - Key? key, + super.key, required this.interfaces, required this.onSelect, required this.dialog - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/screens/settings/dns/clear_dns_cache_dialog.dart b/lib/screens/settings/dns/clear_dns_cache_dialog.dart index 425d11d..b04c889 100644 --- a/lib/screens/settings/dns/clear_dns_cache_dialog.dart +++ b/lib/screens/settings/dns/clear_dns_cache_dialog.dart @@ -5,9 +5,9 @@ class ClearDnsCacheDialog extends StatelessWidget { final void Function() onConfirm; const ClearDnsCacheDialog({ - Key? key, + super.key, required this.onConfirm - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -22,6 +22,7 @@ class ClearDnsCacheDialog extends StatelessWidget { const SizedBox(height: 16), Text( AppLocalizations.of(context)!.clearDnsCache, + textAlign: TextAlign.center, style: TextStyle( fontSize: 24, color: Theme.of(context).colorScheme.onSurface diff --git a/lib/screens/settings/dns_rewrites/dns_rewrites.dart b/lib/screens/settings/dns_rewrites/dns_rewrites.dart index 236b107..8b5ee0d 100644 --- a/lib/screens/settings/dns_rewrites/dns_rewrites.dart +++ b/lib/screens/settings/dns_rewrites/dns_rewrites.dart @@ -19,7 +19,7 @@ import 'package:adguard_home_manager/models/rewrite_rules.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; class DnsRewritesScreen extends StatefulWidget { - const DnsRewritesScreen({Key? key}) : super(key: key); + const DnsRewritesScreen({super.key}); @override State createState() => _DnsRewritesScreenState(); @@ -152,6 +152,7 @@ class _DnsRewritesScreenState extends State { const SizedBox(height: 30), Text( AppLocalizations.of(context)!.loadingRewriteRules, + textAlign: TextAlign.center, style: TextStyle( fontSize: 22, color: Theme.of(context).colorScheme.onSurfaceVariant, @@ -305,6 +306,7 @@ class _DnsRewritesScreenState extends State { const SizedBox(height: 30), Text( AppLocalizations.of(context)!.rewriteRulesNotLoaded, + textAlign: TextAlign.center, style: TextStyle( fontSize: 22, color: Theme.of(context).colorScheme.onSurfaceVariant, diff --git a/lib/screens/settings/safe_search_settings.dart b/lib/screens/settings/safe_search_settings.dart index 4f6713a..99f6ad7 100644 --- a/lib/screens/settings/safe_search_settings.dart +++ b/lib/screens/settings/safe_search_settings.dart @@ -177,10 +177,12 @@ class _SafeSearchSettingsScreenState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - AppLocalizations.of(context)!.enableSafeSearch, - style: const TextStyle( - fontSize: 18 + Flexible( + child: Text( + AppLocalizations.of(context)!.enableSafeSearch, + style: const TextStyle( + fontSize: 18 + ), ), ), Switch( diff --git a/lib/widgets/custom_list_tile_dialog.dart b/lib/widgets/custom_list_tile_dialog.dart index 2e3cff4..3faf8d4 100644 --- a/lib/widgets/custom_list_tile_dialog.dart +++ b/lib/widgets/custom_list_tile_dialog.dart @@ -29,12 +29,14 @@ class CustomListTileDialog extends StatelessWidget { ), const SizedBox(width: 24), ], - Text( - title, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurface, + Flexible( + child: Text( + title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurface, + ), ), ) ], From e01cc5ba65a52dcd5587cf870125b83fa131efd0 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 29 Nov 2023 11:57:14 +0100 Subject: [PATCH 135/177] Removed unused import --- lib/screens/logs/configuration/logs_config_modal.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/screens/logs/configuration/logs_config_modal.dart b/lib/screens/logs/configuration/logs_config_modal.dart index 1b6c641..6352f36 100644 --- a/lib/screens/logs/configuration/logs_config_modal.dart +++ b/lib/screens/logs/configuration/logs_config_modal.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; From 5f9d24cc5fb129a920739cf89d39bb719c513941 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 29 Nov 2023 17:18:35 +0100 Subject: [PATCH 136/177] Changed top items --- .../home/top_items/top_items_lists.dart | 12 +- .../home/top_items/top_items_screen.dart | 324 ++++++++++++++++++ ...{top_items.dart => top_items_section.dart} | 72 ++-- lib/screens/top_items/top_items.dart | 241 ------------- lib/screens/top_items/top_items_modal.dart | 218 ------------ 5 files changed, 365 insertions(+), 502 deletions(-) create mode 100644 lib/screens/home/top_items/top_items_screen.dart rename lib/screens/home/top_items/{top_items.dart => top_items_section.dart} (86%) delete mode 100644 lib/screens/top_items/top_items.dart delete mode 100644 lib/screens/top_items/top_items_modal.dart diff --git a/lib/screens/home/top_items/top_items_lists.dart b/lib/screens/home/top_items/top_items_lists.dart index 2f8820d..7f230fb 100644 --- a/lib/screens/home/top_items/top_items_lists.dart +++ b/lib/screens/home/top_items/top_items_lists.dart @@ -4,7 +4,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/home/top_items/top_items.dart'; +import 'package:adguard_home_manager/screens/home/top_items/top_items_section.dart'; import 'package:adguard_home_manager/functions/number_format.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; @@ -106,7 +106,7 @@ class TopItemsLists extends StatelessWidget { case HomeTopItems.queriedDomains: return Column( children: [ - TopItems( + TopItemsSection( label: AppLocalizations.of(context)!.topQueriedDomains, type: HomeTopItems.queriedDomains, data: statusProvider.serverStatus?.stats.topQueriedDomains ?? [], @@ -134,7 +134,7 @@ class TopItemsLists extends StatelessWidget { case HomeTopItems.blockedDomains: return Column( children: [ - TopItems( + TopItemsSection( label: AppLocalizations.of(context)!.topBlockedDomains, type: HomeTopItems.blockedDomains, data: statusProvider.serverStatus?.stats.topBlockedDomains ?? [], @@ -162,7 +162,7 @@ class TopItemsLists extends StatelessWidget { case HomeTopItems.recurrentClients: return Column( children: [ - TopItems( + TopItemsSection( label: AppLocalizations.of(context)!.topClients, type: HomeTopItems.recurrentClients, data: statusProvider.serverStatus?.stats.topClients ?? [], @@ -186,7 +186,7 @@ class TopItemsLists extends StatelessWidget { return statusProvider.serverStatus!.stats.topUpstreamResponses != null ? Column( children: [ - TopItems( + TopItemsSection( label: AppLocalizations.of(context)!.topUpstreams, type: HomeTopItems.topUpstreams, data: statusProvider.serverStatus?.stats.topUpstreamResponses ?? [], @@ -210,7 +210,7 @@ class TopItemsLists extends StatelessWidget { return statusProvider.serverStatus!.stats.topUpstreamsAvgTime != null ? Column( children: [ - TopItems( + TopItemsSection( label: AppLocalizations.of(context)!.averageUpstreamResponseTime, type: HomeTopItems.avgUpstreamResponseTime, data: statusProvider.serverStatus?.stats.topUpstreamsAvgTime ?? [], diff --git a/lib/screens/home/top_items/top_items_screen.dart b/lib/screens/home/top_items/top_items_screen.dart new file mode 100644 index 0000000..01ac83e --- /dev/null +++ b/lib/screens/home/top_items/top_items_screen.dart @@ -0,0 +1,324 @@ +// ignore_for_file: use_build_context_synchronously + +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:percent_indicator/percent_indicator.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/models/menu_option.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/functions/number_format.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; + +class TopItemsScreen extends StatefulWidget { + final HomeTopItems type; + final String title; + final bool? isClient; + final List> data; + final bool withProgressBar; + final String Function(dynamic) buildValue; + final List options; + final void Function(dynamic)? onTapEntry; + final bool isFullscreen; + + const TopItemsScreen({ + super.key, + required this.type, + required this.title, + this.isClient, + required this.data, + required this.withProgressBar, + required this.buildValue, + required this.options, + this.onTapEntry, + required this.isFullscreen, + }); + + @override + State createState() => _TopItemsScreenState(); +} + +class _TopItemsScreenState extends State { + bool searchActive = false; + final TextEditingController searchController = TextEditingController(); + + List> data = []; + List> screenData = []; + + void search(String value) { + List> newValues = widget.data.where((item) => item.keys.toList()[0].contains(value)).toList(); + setState(() => screenData = newValues); + } + + @override + void initState() { + data = widget.data; + screenData = widget.data; + super.initState(); + } + + @override + Widget build(BuildContext context) { + double total = 0; + for (var element in data) { + total = total + double.parse(element.values.toList()[0].toString()); + } + + if (widget.isFullscreen == true) { + return Dialog.fullscreen( + child: Scaffold( + appBar: AppBar( + title: searchActive == true + ? Padding( + padding: const EdgeInsets.only(bottom: 3), + child: TextFormField( + controller: searchController, + onChanged: search, + decoration: InputDecoration( + 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(widget.title), + leading: searchActive == true ? + IconButton( + onPressed: () => setState(() { + searchActive = false; + searchController.text = ''; + screenData = data; + }), + icon: const Icon(Icons.arrow_back), + tooltip: AppLocalizations.of(context)!.exitSearch, + ) : null, + actions: [ + if (searchActive == false) IconButton( + onPressed: () => setState(() => searchActive = true), + icon: const Icon(Icons.search), + tooltip: AppLocalizations.of(context)!.search, + ), + if (searchActive == true) IconButton( + onPressed: () => setState(() { + searchController.text = ''; + screenData = data; + }), + icon: const Icon(Icons.clear_rounded), + tooltip: AppLocalizations.of(context)!.clearSearch, + ), + const SizedBox(width: 8) + ], + ), + body: _Content( + buildValue: widget.buildValue, + isClient: widget.isClient, + onTapEntry: widget.onTapEntry, + options: widget.options, + screenData: screenData, + total: total, + withProgressBar: widget.withProgressBar, + ), + ), + ); + } + 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: [ + Expanded( + flex: 1, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + IconButton( + onPressed: () => Navigator.pop(context), + icon: const Icon(Icons.clear_rounded), + tooltip: AppLocalizations.of(context)!.close, + ), + ], + ), + ), + Expanded( + flex: 1, + child: TextField( + controller: searchController, + onChanged: search, + decoration: InputDecoration( + filled: true, + fillColor: Theme.of(context).colorScheme.primary.withOpacity(0.1), + hintText: AppLocalizations.of(context)!.search, + prefixIcon: const Icon(Icons.search_rounded), + contentPadding: const EdgeInsets.only(left: 14, bottom: 9, top: 11), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.transparent), + borderRadius: BorderRadius.circular(25.7), + ), + enabledBorder: UnderlineInputBorder( + borderSide: const BorderSide(color: Colors.transparent), + borderRadius: BorderRadius.circular(25.7), + ), + ), + ), + ), + ], + ), + ), + Expanded( + child: _Content( + buildValue: widget.buildValue, + isClient: widget.isClient, + onTapEntry: widget.onTapEntry, + options: widget.options, + screenData: screenData, + total: total, + withProgressBar: widget.withProgressBar, + ), + ), + ], + ), + ), + ); + } + } +} + +class _Content extends StatelessWidget { + final List> screenData; + final bool? isClient; + final List options; + final bool withProgressBar; + final void Function(dynamic)? onTapEntry; + final String Function(dynamic) buildValue; + final double total; + + const _Content({ + required this.screenData, + required this.isClient, + required this.options, + required this.withProgressBar, + required this.onTapEntry, + required this.buildValue, + required this.total, + }); + + @override + Widget build(BuildContext context) { + final statusProvider = Provider.of(context); + + if (screenData.isNotEmpty) { + return ListView.builder( + padding: const EdgeInsets.only(top: 0), + itemCount: screenData.length, + itemBuilder: (context, index) { + String? name; + if (isClient != null && isClient == true) { + try { + name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(screenData[index].keys.toList()[0])).name; + } catch (e) { + // ---- // + } + } + + return OptionsMenu( + options: options, + value: screenData[index].keys.toList()[0], + onTap: onTapEntry != null + ? (v) { + onTapEntry!(v); + Navigator.pop(context); + } + : null, + child: CustomListTile( + title: screenData[index].keys.toList()[0], + trailing: Text( + buildValue(screenData[index].values.toList()[0]), + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + subtitleWidget: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (name != null) ...[ + Text( + name, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurface + ), + ), + const SizedBox(height: 5), + ], + if (withProgressBar == true) Row( + children: [ + SizedBox( + width: 50, + child: Text( + "${doubleFormat((screenData[index].values.toList()[0]/total*100), Platform.localeName)}%", + style: TextStyle( + color: Theme.of(context).listTileTheme.textColor + ), + ), + ), + const SizedBox(width: 10), + Flexible( + child: LinearPercentIndicator( + animation: true, + lineHeight: 4, + animationDuration: 500, + curve: Curves.easeOut, + percent: screenData[index].values.toList()[0]/total, + barRadius: const Radius.circular(5), + progressColor: Theme.of(context).colorScheme.primary, + backgroundColor: Theme.of(context).colorScheme.surfaceVariant, + ), + ), + const SizedBox(width: 10), + ], + ), + ], + ) + ), + ); + } + ); + } + else { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + AppLocalizations.of(context)!.noItemsSearch, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ), + ); + } + } +} \ No newline at end of file diff --git a/lib/screens/home/top_items/top_items.dart b/lib/screens/home/top_items/top_items_section.dart similarity index 86% rename from lib/screens/home/top_items/top_items.dart rename to lib/screens/home/top_items/top_items_section.dart index 00fa63c..f084b3f 100644 --- a/lib/screens/home/top_items/top_items.dart +++ b/lib/screens/home/top_items/top_items_section.dart @@ -7,15 +7,14 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/home/top_items/row_item.dart'; -import 'package:adguard_home_manager/screens/top_items/top_items_modal.dart'; -import 'package:adguard_home_manager/screens/top_items/top_items.dart'; +import 'package:adguard_home_manager/screens/home/top_items/top_items_screen.dart'; import 'package:adguard_home_manager/widgets/custom_pie_chart.dart'; import 'package:adguard_home_manager/models/menu_option.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; -class TopItems extends StatefulWidget { +class TopItemsSection extends StatefulWidget { final HomeTopItems type; final String label; final List> data; @@ -25,7 +24,7 @@ class TopItems extends StatefulWidget { final List menuOptions; final void Function(dynamic)? onTapEntry; - const TopItems({ + const TopItemsSection({ super.key, required this.type, required this.label, @@ -38,10 +37,10 @@ class TopItems extends StatefulWidget { }); @override - State createState() => _TopItemsState(); + State createState() => _TopItemsState(); } -class _TopItemsState extends State { +class _TopItemsState extends State { bool _showChart = true; final colors = [ @@ -289,38 +288,37 @@ class _TopItemsState extends State { children: [ TextButton( onPressed: () => { - if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => TopItemsModal( - type: widget.type, - title: widget.label, - isClient: widget.type == HomeTopItems.recurrentClients, - data: widget.data, - withProgressBar: widget.withProgressBar, - buildValue: widget.buildValue, - options: widget.menuOptions, - onTapEntry: widget.onTapEntry, - ) + 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) => TopItemsScreen( + type: widget.type, + title: widget.label, + isClient: widget.type == HomeTopItems.recurrentClients, + data: widget.data, + withProgressBar: widget.withProgressBar, + buildValue: widget.buildValue, + options: widget.menuOptions, + onTapEntry: widget.onTapEntry, + isFullscreen: !(width > 700 || !(Platform.isAndroid | Platform.isIOS)), ) - } - else { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => TopItemsScreen( - type: widget.type, - title: widget.label, - isClient: widget.type == HomeTopItems.recurrentClients, - data: widget.data, - withProgressBar: widget.withProgressBar, - buildValue: widget.buildValue, - menuOptions: widget.menuOptions, - onTapEntry: widget.onTapEntry, - ) - ) - ) - } + ) }, child: Row( mainAxisSize: MainAxisSize.min, diff --git a/lib/screens/top_items/top_items.dart b/lib/screens/top_items/top_items.dart deleted file mode 100644 index 498ce75..0000000 --- a/lib/screens/top_items/top_items.dart +++ /dev/null @@ -1,241 +0,0 @@ -// ignore_for_file: use_build_context_synchronously - -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:percent_indicator/percent_indicator.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/models/menu_option.dart'; -import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/providers/status_provider.dart'; -import 'package:adguard_home_manager/functions/snackbar.dart'; -import 'package:adguard_home_manager/functions/number_format.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; - -class TopItemsScreen extends StatefulWidget { - final HomeTopItems type; - final String title; - final bool? isClient; - final List> data; - final bool withProgressBar; - final String Function(dynamic) buildValue; - final List menuOptions; - final void Function(dynamic)? onTapEntry; - - const TopItemsScreen({ - super.key, - required this.type, - required this.title, - this.isClient, - required this.data, - required this.withProgressBar, - required this.buildValue, - required this.menuOptions, - this.onTapEntry, - }); - - @override - State createState() => _TopItemsScreenState(); -} - -class _TopItemsScreenState extends State { - bool searchActive = false; - final TextEditingController searchController = TextEditingController(); - - List> data = []; - List> screenData = []; - - void search(String value) { - List> newValues = widget.data.where((item) => item.keys.toList()[0].contains(value)).toList(); - setState(() => screenData = newValues); - } - - @override - void initState() { - data = widget.data; - screenData = widget.data; - super.initState(); - } - - @override - Widget build(BuildContext context) { - final statusProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - - double total = 0; - for (var element in data) { - total = total + double.parse(element.values.toList()[0].toString()); - } - - return Scaffold( - appBar: AppBar( - title: searchActive == true - ? Padding( - padding: const EdgeInsets.only(bottom: 3), - child: TextFormField( - controller: searchController, - onChanged: search, - decoration: InputDecoration( - 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(widget.title), - leading: searchActive == true ? - IconButton( - onPressed: () => setState(() { - searchActive = false; - searchController.text = ''; - screenData = data; - }), - icon: const Icon(Icons.arrow_back), - tooltip: AppLocalizations.of(context)!.exitSearch, - ) : null, - actions: [ - if (searchActive == false) IconButton( - onPressed: () => setState(() => searchActive = true), - icon: const Icon(Icons.search), - tooltip: AppLocalizations.of(context)!.search, - ), - if (searchActive == true) IconButton( - onPressed: () => setState(() { - searchController.text = ''; - screenData = data; - }), - icon: const Icon(Icons.clear_rounded), - tooltip: AppLocalizations.of(context)!.clearSearch, - ), - const SizedBox(width: 10) - ], - bottom: PreferredSize( - preferredSize: const Size(double.maxFinite, 1), - child: Container( - width: double.maxFinite, - height: 1, - decoration: BoxDecoration( - color: searchActive == true - ? Colors.grey.withOpacity(0.5) - : Colors.transparent - ), - ), - ), - ), - body: RefreshIndicator( - onRefresh: () async { - final result = await statusProvider.getServerStatus(); - if (mounted && result == false) { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.serverStatusNotRefreshed, - color: Colors.red - ); - } - }, - child: screenData.isNotEmpty - ? ListView.builder( - itemCount: screenData.length, - itemBuilder: (context, index) { - String? name; - if (widget.isClient != null && widget.isClient == true) { - try { - name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(screenData[index].keys.toList()[0])).name; - } catch (e) { - // ---- // - } - } - - return OptionsMenu( - value: screenData[index].keys.toList()[0], - options: widget.menuOptions, - onTap: widget.onTapEntry != null - ? (v) { - widget.onTapEntry!(v); - Navigator.pop(context); - } - : null, - child: CustomListTile( - title: screenData[index].keys.toList()[0], - trailing: Text( - widget.buildValue(screenData[index].values.toList()[0]), - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - subtitleWidget: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (name != null) ...[ - Text( - name, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurface - ), - ), - const SizedBox(height: 5), - ], - if (widget.withProgressBar == true) Row( - children: [ - SizedBox( - width: 50, - child: Text( - "${doubleFormat((screenData[index].values.toList()[0]/total*100), Platform.localeName)}%", - style: TextStyle( - color: Theme.of(context).listTileTheme.textColor - ), - ), - ), - const SizedBox(width: 10), - Flexible( - child: LinearPercentIndicator( - animation: true, - lineHeight: 4, - animationDuration: 500, - curve: Curves.easeOut, - percent: screenData[index].values.toList()[0]/total, - barRadius: const Radius.circular(5), - progressColor: Theme.of(context).colorScheme.primary, - backgroundColor: Theme.of(context).colorScheme.surfaceVariant, - ), - ), - const SizedBox(width: 10), - ], - ), - ], - ) - ), - ); - } - ) - : Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - AppLocalizations.of(context)!.noItemsSearch, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - ) - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/top_items/top_items_modal.dart b/lib/screens/top_items/top_items_modal.dart deleted file mode 100644 index 36ea793..0000000 --- a/lib/screens/top_items/top_items_modal.dart +++ /dev/null @@ -1,218 +0,0 @@ -// ignore_for_file: use_build_context_synchronously - -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:percent_indicator/percent_indicator.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/models/menu_option.dart'; -import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/functions/number_format.dart'; -import 'package:adguard_home_manager/providers/status_provider.dart'; - -class TopItemsModal extends StatefulWidget { - final HomeTopItems type; - final String title; - final bool? isClient; - final List> data; - final bool withProgressBar; - final String Function(dynamic) buildValue; - final List options; - final void Function(dynamic)? onTapEntry; - - const TopItemsModal({ - super.key, - required this.type, - required this.title, - this.isClient, - required this.data, - required this.withProgressBar, - required this.buildValue, - required this.options, - this.onTapEntry, - }); - - @override - State createState() => _TopItemsModalState(); -} - -class _TopItemsModalState extends State { - bool searchActive = false; - final TextEditingController searchController = TextEditingController(); - - List> data = []; - List> screenData = []; - - void search(String value) { - List> newValues = widget.data.where((item) => item.keys.toList()[0].contains(value)).toList(); - setState(() => screenData = newValues); - } - - @override - void initState() { - data = widget.data; - screenData = widget.data; - super.initState(); - } - - @override - Widget build(BuildContext context) { - final statusProvider = Provider.of(context); - - double total = 0; - for (var element in data) { - total = total + double.parse(element.values.toList()[0].toString()); - } - - return Dialog( - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 500 - ), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - flex: 1, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - IconButton( - onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.clear_rounded), - tooltip: AppLocalizations.of(context)!.close, - ), - ], - ), - ), - Expanded( - flex: 1, - child: TextField( - controller: searchController, - onChanged: search, - decoration: InputDecoration( - filled: true, - fillColor: Theme.of(context).colorScheme.primary.withOpacity(0.1), - hintText: AppLocalizations.of(context)!.search, - prefixIcon: const Icon(Icons.search_rounded), - contentPadding: const EdgeInsets.only(left: 14, bottom: 9, top: 11), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide(color: Colors.transparent), - borderRadius: BorderRadius.circular(25.7), - ), - enabledBorder: UnderlineInputBorder( - borderSide: const BorderSide(color: Colors.transparent), - borderRadius: BorderRadius.circular(25.7), - ), - ), - ), - ) - ], - ), - ), - if (screenData.isNotEmpty) Flexible( - child: ListView.builder( - padding: const EdgeInsets.only(top: 0), - itemCount: screenData.length, - itemBuilder: (context, index) { - String? name; - if (widget.isClient != null && widget.isClient == true) { - try { - name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(screenData[index].keys.toList()[0])).name; - } catch (e) { - // ---- // - } - } - - return OptionsMenu( - options: widget.options, - value: screenData[index].keys.toList()[0], - onTap: widget.onTapEntry != null - ? (v) { - widget.onTapEntry!(v); - Navigator.pop(context); - } - : null, - child: CustomListTile( - title: screenData[index].keys.toList()[0], - trailing: Text( - widget.buildValue(screenData[index].values.toList()[0]), - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - subtitleWidget: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (name != null) ...[ - Text( - name, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurface - ), - ), - const SizedBox(height: 5), - ], - if (widget.withProgressBar == true) Row( - children: [ - SizedBox( - width: 50, - child: Text( - "${doubleFormat((screenData[index].values.toList()[0]/total*100), Platform.localeName)}%", - style: TextStyle( - color: Theme.of(context).listTileTheme.textColor - ), - ), - ), - const SizedBox(width: 10), - Flexible( - child: LinearPercentIndicator( - animation: true, - lineHeight: 4, - animationDuration: 500, - curve: Curves.easeOut, - percent: screenData[index].values.toList()[0]/total, - barRadius: const Radius.circular(5), - progressColor: Theme.of(context).colorScheme.primary, - backgroundColor: Theme.of(context).colorScheme.surfaceVariant, - ), - ), - const SizedBox(width: 10), - ], - ), - ], - ) - ), - ); - } - ), - ), - if (screenData.isEmpty) Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - AppLocalizations.of(context)!.noItemsSearch, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - ) - ], - ), - ), - ); - } -} \ No newline at end of file From d8eeb7a51b4c78697318263f3616f50577a8a684 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 29 Nov 2023 17:28:50 +0100 Subject: [PATCH 137/177] Improved text scale factor --- .../home/management_modal/main_switch.dart | 2 +- lib/screens/home/server_status.dart | 28 ++----------------- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/lib/screens/home/management_modal/main_switch.dart b/lib/screens/home/management_modal/main_switch.dart index 3dbe0ec..f746b0e 100644 --- a/lib/screens/home/management_modal/main_switch.dart +++ b/lib/screens/home/management_modal/main_switch.dart @@ -175,7 +175,7 @@ class _BottomRowState extends State<_BottomRow> { Widget build(BuildContext context) { final statusProvider = Provider.of(context); - final textScale = MediaQuery.of(context).textScaleFactor; + final textScale = MediaQuery.of(context).textScaler.scale(1); return Container( height: Platform.isMacOS || Platform.isLinux || Platform.isWindows diff --git a/lib/screens/home/server_status.dart b/lib/screens/home/server_status.dart index a0e1715..b716c82 100644 --- a/lib/screens/home/server_status.dart +++ b/lib/screens/home/server_status.dart @@ -16,31 +16,7 @@ class ServerStatusWidget extends StatelessWidget { @override Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; - final textScaleFactor = MediaQuery.of(context).textScaleFactor; - - double boxSize() { - if (textScaleFactor < 1 || (textScaleFactor >= 1 && textScaleFactor < 1.15)) { - return 65; - } - else if (textScaleFactor >= 1.15 && textScaleFactor < 1.3) { - return 75; - } - else if (textScaleFactor >= 1.3 && textScaleFactor < 1.45) { - return 80; - } - else if (textScaleFactor >= 1.45 && textScaleFactor < 1.6) { - return 85; - } - else if (textScaleFactor >= 1.6 && textScaleFactor < 1.85) { - return 100; - } - else if (textScaleFactor >= 1.85) { - return 110; - } - else { - return 65; - } - } + final textScaleFactor = MediaQuery.of(context).textScaler.scale(1); return Padding( padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16), @@ -65,7 +41,7 @@ class ServerStatusWidget extends StatelessWidget { crossAxisCount: width > 700 ? 4 : 2, crossAxisSpacing: 10, mainAxisSpacing: 10, - mainAxisExtent: boxSize() + mainAxisExtent: 70*textScaleFactor ), children: [ StatusBox( From 232fedd27377655d76096304728d93d2eaa80b5c Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 30 Nov 2023 11:07:15 +0100 Subject: [PATCH 138/177] Updated app version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index bfddb2f..c769999 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.12.1+112 +version: 2.12.2+113 environment: sdk: '>=2.18.1 <3.0.0' From 8e1dd6e22e4e86e974e898e3b905d2f893157f71 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 1 Dec 2023 02:03:13 +0100 Subject: [PATCH 139/177] Improved settings --- lib/screens/settings/settings.dart | 143 +++++++++++++++++------------ 1 file changed, 85 insertions(+), 58 deletions(-) diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index 8d5f384..8edbc11 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -32,7 +32,7 @@ import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class Settings extends StatelessWidget { - const Settings({Key? key}) : super(key: key); + const Settings({super.key}); @override Widget build(BuildContext context) { @@ -55,13 +55,13 @@ class Settings extends StatelessWidget { ), ), ), - child: const SettingsWidget( + child: const _SettingsWidget( twoColumns: true, ), ); } else { - return const SettingsWidget( + return const _SettingsWidget( twoColumns: false, ); } @@ -69,19 +69,18 @@ class Settings extends StatelessWidget { ); } } -class SettingsWidget extends StatefulWidget { +class _SettingsWidget extends StatefulWidget { final bool twoColumns; - const SettingsWidget({ - Key? key, + const _SettingsWidget({ required this.twoColumns, - }) : super(key: key); + }); @override - State createState() => _SettingsWidgetState(); + State<_SettingsWidget> createState() => _SettingsWidgetState(); } -class _SettingsWidgetState extends State { +class _SettingsWidgetState extends State<_SettingsWidget> { @override void initState() { Provider.of(context, listen: false).setSelectedSettingsScreen(screen: null); @@ -100,43 +99,6 @@ class _SettingsWidgetState extends State { appConfigProvider.setSelectedSettingsScreen(screen: null); } - Widget settingsTile({ - required String title, - required String subtitle, - required IconData icon, - Widget? trailing, - required Widget screenToNavigate, - required int thisItem - }) { - if (widget.twoColumns) { - return CustomSettingsTile( - title: title, - subtitle: subtitle, - icon: icon, - trailing: trailing, - thisItem: thisItem, - selectedItem: appConfigProvider.selectedSettingsScreen, - onTap: () { - appConfigProvider.setSelectedSettingsScreen(screen: thisItem, notify: true); - SplitView.of(context).setSecondary(screenToNavigate); - }, - ); - } - else { - return CustomListTile( - title: title, - subtitle: subtitle, - icon: icon, - trailing: trailing, - onTap: () { - Navigator.of(context).push( - MaterialPageRoute(builder: (context) => screenToNavigate) - ); - }, - ); - } - } - return Scaffold( body: NestedScrollView( headerSliverBuilder: (context, innerBoxIsScrolled) => [ @@ -169,28 +131,31 @@ class _SettingsWidgetState extends State { serversProvider.apiClient2 != null ) ...[ SectionLabel(label: AppLocalizations.of(context)!.serverSettings), - settingsTile( + _SettingsTile( icon: Icons.search_rounded, title: AppLocalizations.of(context)!.safeSearch, subtitle: AppLocalizations.of(context)!.safeSearchSettings, thisItem: 0, screenToNavigate: const SafeSearchSettingsScreen(), + twoColumns: widget.twoColumns, ), - settingsTile( + _SettingsTile( icon: Icons.lock_rounded, title: AppLocalizations.of(context)!.accessSettings, subtitle: AppLocalizations.of(context)!.accessSettingsDescription, thisItem: 1, screenToNavigate: const AccessSettings(), + twoColumns: widget.twoColumns, ), - settingsTile( + _SettingsTile( icon: Icons.install_desktop_rounded, title: AppLocalizations.of(context)!.dhcpSettings, subtitle: AppLocalizations.of(context)!.dhcpSettingsDescription, thisItem: 2, screenToNavigate: const DhcpScreen(), + twoColumns: widget.twoColumns, ), - settingsTile( + _SettingsTile( icon: Icons.dns_rounded, title: AppLocalizations.of(context)!.dnsSettings, subtitle: AppLocalizations.of(context)!.dnsSettingsDescription, @@ -198,22 +163,25 @@ class _SettingsWidgetState extends State { screenToNavigate: DnsSettings( splitView: widget.twoColumns, ), + twoColumns: widget.twoColumns, ), - settingsTile( + _SettingsTile( icon: Icons.security_rounded, title: AppLocalizations.of(context)!.encryptionSettings, subtitle: AppLocalizations.of(context)!.encryptionSettingsDescription, thisItem: 4, screenToNavigate: const EncryptionSettings(), + twoColumns: widget.twoColumns, ), - settingsTile( + _SettingsTile( icon: Icons.route_rounded, title: AppLocalizations.of(context)!.dnsRewrites, subtitle: AppLocalizations.of(context)!.dnsRewritesDescription, thisItem: 5, screenToNavigate: const DnsRewritesScreen(), + twoColumns: widget.twoColumns, ), - if (serversProvider.updateAvailable.data != null) settingsTile( + if (serversProvider.updateAvailable.data != null) _SettingsTile( icon: Icons.system_update_rounded, title: AppLocalizations.of(context)!.updates, subtitle: AppLocalizations.of(context)!.updatesDescription, @@ -231,24 +199,27 @@ class _SettingsWidgetState extends State { : null, thisItem: 6, screenToNavigate: const UpdateScreen(), + twoColumns: widget.twoColumns, ), - settingsTile( + _SettingsTile( icon: Icons.info_rounded, title: AppLocalizations.of(context)!.serverInformation, subtitle: AppLocalizations.of(context)!.serverInformationDescription, thisItem: 7, screenToNavigate: const ServerInformation(), + twoColumns: widget.twoColumns, ), ], SectionLabel(label: AppLocalizations.of(context)!.appSettings), - settingsTile( + _SettingsTile( icon: Icons.palette_rounded, title: AppLocalizations.of(context)!.customization, subtitle: AppLocalizations.of(context)!.customizationDescription, thisItem: 8, screenToNavigate: const Customization(), + twoColumns: widget.twoColumns, ), - settingsTile( + _SettingsTile( icon: Icons.storage_rounded, title: AppLocalizations.of(context)!.servers, subtitle: serversProvider.selectedServer != null @@ -258,20 +229,23 @@ class _SettingsWidgetState extends State { : AppLocalizations.of(context)!.noServerSelected, thisItem: 9, screenToNavigate: const Servers(), + twoColumns: widget.twoColumns, ), - settingsTile( + _SettingsTile( icon: Icons.settings, title: AppLocalizations.of(context)!.generalSettings, subtitle: AppLocalizations.of(context)!.generalSettingsDescription, thisItem: 10, screenToNavigate: const GeneralSettings(), + twoColumns: widget.twoColumns, ), - settingsTile( + _SettingsTile( icon: Icons.build_outlined, title: AppLocalizations.of(context)!.advancedSettings, subtitle: AppLocalizations.of(context)!.advancedSetupDescription, thisItem: 11, screenToNavigate: const AdvancedSettings(), + twoColumns: widget.twoColumns, ), SectionLabel(label: AppLocalizations.of(context)!.aboutApp), CustomListTile( @@ -319,4 +293,57 @@ class _SettingsWidgetState extends State { ) ); } +} + +class _SettingsTile extends StatelessWidget { + final String title; + final String subtitle; + final IconData icon; + final Widget? trailing; + final Widget screenToNavigate; + final int thisItem; + final bool twoColumns; + + const _SettingsTile({ + required this.title, + required this.subtitle, + required this.icon, + this.trailing, + required this.screenToNavigate, + required this.thisItem, + required this.twoColumns + }); + + @override + Widget build(BuildContext context) { + final appConfigProvider = Provider.of(context); + + if (twoColumns) { + return CustomSettingsTile( + title: title, + subtitle: subtitle, + icon: icon, + trailing: trailing, + thisItem: thisItem, + selectedItem: appConfigProvider.selectedSettingsScreen, + onTap: () { + appConfigProvider.setSelectedSettingsScreen(screen: thisItem, notify: true); + SplitView.of(context).setSecondary(screenToNavigate); + }, + ); + } + else { + return CustomListTile( + title: title, + subtitle: subtitle, + icon: icon, + trailing: trailing, + onTap: () { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => screenToNavigate) + ); + }, + ); + } + } } \ No newline at end of file From b42306cde361953a6b793ab15a6f7219fc94a171 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 1 Dec 2023 02:14:12 +0100 Subject: [PATCH 140/177] Fixed screen change desktop mode reorder items screen --- .../general_settings/general_settings.dart | 20 ++- .../reorderable_top_items_home.dart | 142 +++++++----------- lib/screens/settings/settings.dart | 2 +- 3 files changed, 66 insertions(+), 98 deletions(-) diff --git a/lib/screens/settings/general_settings/general_settings.dart b/lib/screens/settings/general_settings/general_settings.dart index 4625136..20e13e8 100644 --- a/lib/screens/settings/general_settings/general_settings.dart +++ b/lib/screens/settings/general_settings/general_settings.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:provider/provider.dart'; import 'package:store_checker/store_checker.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -20,7 +21,12 @@ import 'package:adguard_home_manager/functions/app_update_download_link.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class GeneralSettings extends StatefulWidget { - const GeneralSettings({Key? key}) : super(key: key); + final bool splitView; + + const GeneralSettings({ + super.key, + required this.splitView, + }); @override State createState() => _GeneralSettingsState(); @@ -191,11 +197,13 @@ class _GeneralSettingsState extends State { icon: Icons.reorder_rounded, title: AppLocalizations.of(context)!.topItemsOrder, subtitle: AppLocalizations.of(context)!.topItemsOrderDescription, - onTap: () => Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const ReorderableTopItemsHome() - ) - ) + onTap: () => widget.splitView == true + ? SplitView.of(context).push(const ReorderableTopItemsHome()) + : Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const ReorderableTopItemsHome() + ) + ) ), CustomListTile( icon: Icons.donut_large_rounded, diff --git a/lib/screens/settings/general_settings/reorderable_top_items_home.dart b/lib/screens/settings/general_settings/reorderable_top_items_home.dart index 2b7bfe1..9d5e8ed 100644 --- a/lib/screens/settings/general_settings/reorderable_top_items_home.dart +++ b/lib/screens/settings/general_settings/reorderable_top_items_home.dart @@ -136,42 +136,6 @@ class _ReorderableTopItemsHomeState extends State { } } - Future onWillPopScope(bool popInvoked) async { - if (!listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList)) { - await showDialog( - context: context, - useRootNavigator: false, - builder: (ctx) => AlertDialog( - title: Text(AppLocalizations.of(context)!.discardChanges), - content: Text(AppLocalizations.of(context)!.discardChangesDescription), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () { - Navigator.pop(context); - Navigator.pop(context); - }, - child: Text(AppLocalizations.of(context)!.confirm) - ), - const SizedBox(width: 8), - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) - ), - ], - ) - ], - ) - ); - return false; - } - else { - return true; - } - } - void saveSettings() async { final result = await appConfigProvider.setHomeTopItemsOrder(homeTopItemsList); if (result == true) { @@ -190,64 +154,60 @@ class _ReorderableTopItemsHomeState extends State { } } - return PopScope( - canPop: listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList), - onPopInvoked: onWillPopScope, - child: Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context)!.topItemsOrder), - surfaceTintColor: isDesktop(width) ? Colors.transparent : null, - actions: [ - IconButton( - onPressed: !listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList) - ? () => saveSettings() - : null, - icon: const Icon(Icons.save_rounded), - tooltip: AppLocalizations.of(context)!.save, - ), - const SizedBox(width: 8) - ], - ), - body: Column( - children: [ - Card( - margin: const EdgeInsets.all(16), - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Icon( - Icons.info_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 16), - Flexible( - child: Text(AppLocalizations.of(context)!.topItemsReorderInfo) - ) - ], - ), - ), - ), - Expanded( - child: reorderable_list_library.ReorderableList( - onReorder: _reorderCallback, - onReorderDone: _reorderDone, - child: ListView.builder( - itemBuilder: (context, index) => reorderable_list_library.ReorderableItem( - key: renderItems[index].key, - childBuilder: (context, state) => _Item( - tileWidget: tile(renderItems[index].title), - isFirst: index == 0, - isLast: index == renderItems.length - 1, - state: state - ), + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.topItemsOrder), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, + actions: [ + IconButton( + onPressed: !listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList) + ? () => saveSettings() + : null, + icon: const Icon(Icons.save_rounded), + tooltip: AppLocalizations.of(context)!.save, + ), + const SizedBox(width: 8) + ], + ), + body: Column( + children: [ + Card( + margin: const EdgeInsets.all(16), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Icon( + Icons.info_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), - itemCount: renderItems.length, - ) + const SizedBox(width: 16), + Flexible( + child: Text(AppLocalizations.of(context)!.topItemsReorderInfo) + ) + ], ), ), - ], - ), + ), + Expanded( + child: reorderable_list_library.ReorderableList( + onReorder: _reorderCallback, + onReorderDone: _reorderDone, + child: ListView.builder( + itemBuilder: (context, index) => reorderable_list_library.ReorderableItem( + key: renderItems[index].key, + childBuilder: (context, state) => _Item( + tileWidget: tile(renderItems[index].title), + isFirst: index == 0, + isLast: index == renderItems.length - 1, + state: state + ), + ), + itemCount: renderItems.length, + ) + ), + ), + ], ), ); } diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index 8edbc11..6efdd9b 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -236,7 +236,7 @@ class _SettingsWidgetState extends State<_SettingsWidget> { title: AppLocalizations.of(context)!.generalSettings, subtitle: AppLocalizations.of(context)!.generalSettingsDescription, thisItem: 10, - screenToNavigate: const GeneralSettings(), + screenToNavigate: GeneralSettings(splitView: widget.twoColumns), twoColumns: widget.twoColumns, ), _SettingsTile( From ac6e8f700035fc969d9c3c769c8afa21f5f5657b Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 1 Dec 2023 02:45:17 +0100 Subject: [PATCH 141/177] Improved reorderable list --- .../reorderable_top_items_home.dart | 150 +++++++++++------- 1 file changed, 91 insertions(+), 59 deletions(-) diff --git a/lib/screens/settings/general_settings/reorderable_top_items_home.dart b/lib/screens/settings/general_settings/reorderable_top_items_home.dart index 9d5e8ed..46e3a58 100644 --- a/lib/screens/settings/general_settings/reorderable_top_items_home.dart +++ b/lib/screens/settings/general_settings/reorderable_top_items_home.dart @@ -1,9 +1,9 @@ -// ignore_for_file: use_build_context_synchronously +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:flutter_reorderable_list/flutter_reorderable_list.dart' as reorderable_list_library; +import 'package:flutter_reorderable_list/flutter_reorderable_list.dart' as reorderable_list; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; @@ -138,6 +138,7 @@ class _ReorderableTopItemsHomeState extends State { void saveSettings() async { final result = await appConfigProvider.setHomeTopItemsOrder(homeTopItemsList); + if (!mounted) return; if (result == true) { showSnacbkar( appConfigProvider: appConfigProvider, @@ -153,6 +154,10 @@ class _ReorderableTopItemsHomeState extends State { ); } } + + final draggingMode = Platform.isAndroid + ? DraggingMode.android + : DraggingMode.iOS; return Scaffold( appBar: AppBar( @@ -190,18 +195,34 @@ class _ReorderableTopItemsHomeState extends State { ), ), Expanded( - child: reorderable_list_library.ReorderableList( + child: reorderable_list.ReorderableList( onReorder: _reorderCallback, onReorderDone: _reorderDone, child: ListView.builder( - itemBuilder: (context, index) => reorderable_list_library.ReorderableItem( + itemBuilder: (context, index) => reorderable_list.ReorderableItem( key: renderItems[index].key, - childBuilder: (context, state) => _Item( - tileWidget: tile(renderItems[index].title), - isFirst: index == 0, - isLast: index == renderItems.length - 1, - state: state - ), + childBuilder: (context, state) { + if (draggingMode == DraggingMode.android) { + return reorderable_list.DelayedReorderableListener( + child: _Tile( + draggingMode: draggingMode, + isFirst: index == 0, + isLast: index == renderItems.length - 1, + state: state, + tileWidget: tile(renderItems[index].title), + ), + ); + } + else { + return _Tile( + draggingMode: draggingMode, + isFirst: index == 0, + isLast: index == renderItems.length - 1, + state: state, + tileWidget: tile(renderItems[index].title), + ); + } + }, ), itemCount: renderItems.length, ) @@ -213,68 +234,79 @@ class _ReorderableTopItemsHomeState extends State { } } -class _Item extends StatelessWidget { +class _Tile extends StatelessWidget { final Widget tileWidget; final bool isFirst; final bool isLast; - final reorderable_list_library.ReorderableItemState state; + final reorderable_list.ReorderableItemState state; + final DraggingMode draggingMode; - const _Item({ + const _Tile({ required this.tileWidget, required this.isFirst, required this.isLast, required this.state, + required this.draggingMode }); @override Widget build(BuildContext context) { - BoxDecoration decoration; - - if ( - state == reorderable_list_library.ReorderableItemState.dragProxy || - state == reorderable_list_library.ReorderableItemState.dragProxyFinished - ) { - decoration = BoxDecoration( - color: Theme.of(context).colorScheme.surface.withOpacity(0.7) - ); - } - else { - bool placeholder = state == reorderable_list_library.ReorderableItemState.placeholder; - decoration = BoxDecoration( - border: Border( - top: isFirst && !placeholder ? BorderSide( - width: 1, - color: Theme.of(context).colorScheme.primary.withOpacity(0.1) - ) : BorderSide.none, - bottom: isLast && placeholder ? BorderSide.none : BorderSide( - width: 1, - color: Theme.of(context).colorScheme.primary.withOpacity(0.1) - ), - ), - ); - } - - return reorderable_list_library.DelayedReorderableListener( - child: Container( - decoration: decoration, - child: SafeArea( - top: false, - bottom: false, - child: Opacity( - opacity: state == reorderable_list_library.ReorderableItemState.placeholder ? 0.0 : 1.0, - child: IntrinsicHeight( - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: tileWidget - ), - ], - ), + BoxDecoration getDecoration() { + if ( + state == reorderable_list.ReorderableItemState.dragProxy || + state == reorderable_list.ReorderableItemState.dragProxyFinished + ) { + return BoxDecoration( + color: Theme.of(context).colorScheme.surface.withOpacity(0.7) + ); + } + else { + bool placeholder = state == reorderable_list.ReorderableItemState.placeholder; + return BoxDecoration( + border: Border( + top: isFirst && !placeholder ? BorderSide( + width: 1, + color: Theme.of(context).colorScheme.primary.withOpacity(0.1) + ) : BorderSide.none, + bottom: isLast && placeholder ? BorderSide.none : BorderSide( + width: 1, + color: Theme.of(context).colorScheme.primary.withOpacity(0.1) ), - ) - ), - ) + ), + ); + } + } + + return Container( + decoration: getDecoration(), + child: SafeArea( + top: false, + bottom: false, + child: Opacity( + opacity: state == reorderable_list.ReorderableItemState.placeholder ? 0.0 : 1.0, + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: tileWidget + ), + if (draggingMode == DraggingMode.iOS) reorderable_list.ReorderableListener( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Center( + child: Icon( + Icons.reorder, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ), + ) + ], + ), + ), + ) + ), ); } } \ No newline at end of file From 1f74da906a5544ea2764ee9b563fa2b733650695 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 1 Dec 2023 02:50:55 +0100 Subject: [PATCH 142/177] Fix --- lib/screens/filters/list_options_menu.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/screens/filters/list_options_menu.dart b/lib/screens/filters/list_options_menu.dart index 4cb6865..32e4610 100644 --- a/lib/screens/filters/list_options_menu.dart +++ b/lib/screens/filters/list_options_menu.dart @@ -56,7 +56,8 @@ class ListOptionsMenu extends StatelessWidget { ); processModal.close(); - + + if (!context.mounted) return; if (result == true) { showSnacbkar( appConfigProvider: appConfigProvider, From 2511ac2c24c2d6b52eaf9ad21681d782193867ef Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 1 Dec 2023 02:52:10 +0100 Subject: [PATCH 143/177] Updated app version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index c769999..32d01fc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.12.2+113 +version: 2.12.3+114 environment: sdk: '>=2.18.1 <3.0.0' From 83ea5891876f4d690c579ab361cc8d981dec97a5 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 8 Dec 2023 20:43:45 +0100 Subject: [PATCH 144/177] Support AGH without DHCP server --- lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/models/dhcp.dart | 4 +- lib/providers/dhcp_provider.dart | 4 +- lib/screens/settings/dhcp/dhcp.dart | 418 +++++++----------- .../settings/dhcp/dhcp_main_button.dart | 76 ++++ .../settings/dhcp/dhcp_not_available.dart | 47 ++ lib/services/api_client.dart | 7 +- lib/services/http_requests.dart | 1 + 9 files changed, 291 insertions(+), 274 deletions(-) create mode 100644 lib/screens/settings/dhcp/dhcp_main_button.dart create mode 100644 lib/screens/settings/dhcp/dhcp_not_available.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index b9ba629..fb875cc 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -688,5 +688,7 @@ "yourVersion": "Your version: {version}", "minimumRequiredVersion": "Minimum required version: {version}", "topUpstreams": "Top upstreams", - "averageUpstreamResponseTime": "Average upstream response time" + "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." } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 0e1566c..cf328dc 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -688,5 +688,7 @@ "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" + "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." } \ No newline at end of file diff --git a/lib/models/dhcp.dart b/lib/models/dhcp.dart index 21b460b..9e7f7ca 100644 --- a/lib/models/dhcp.dart +++ b/lib/models/dhcp.dart @@ -1,9 +1,11 @@ import 'dart:convert'; class DhcpModel { + bool dhcpAvailable; List networkInterfaces; - DhcpStatus dhcpStatus; + DhcpStatus? dhcpStatus; DhcpModel({ + required this.dhcpAvailable, required this.networkInterfaces, required this.dhcpStatus, }); diff --git a/lib/providers/dhcp_provider.dart b/lib/providers/dhcp_provider.dart index 637f870..4fbd66b 100644 --- a/lib/providers/dhcp_provider.dart +++ b/lib/providers/dhcp_provider.dart @@ -69,7 +69,7 @@ class DhcpProvider with ChangeNotifier { 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; } @@ -90,7 +90,7 @@ class DhcpProvider with ChangeNotifier { if (result.successful == true) { DhcpModel data = dhcp!; - data.dhcpStatus.staticLeases.add(lease); + data.dhcpStatus!.staticLeases.add(lease); setDhcpData(data); return result; } diff --git a/lib/screens/settings/dhcp/dhcp.dart b/lib/screens/settings/dhcp/dhcp.dart index 88f1dad..fff464e 100644 --- a/lib/screens/settings/dhcp/dhcp.dart +++ b/lib/screens/settings/dhcp/dhcp.dart @@ -7,8 +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/widgets/section_label.dart'; +import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_not_available.dart'; import 'package:adguard_home_manager/widgets/confirm_action_modal.dart'; +import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_main_button.dart'; +import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_leases.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/select_interface_modal.dart'; @@ -22,7 +24,7 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; class DhcpScreen extends StatefulWidget { - const DhcpScreen({Key? key}) : super(key: key); + const DhcpScreen({super.key}); @override State createState() => _DhcpScreenState(); @@ -55,24 +57,25 @@ class _DhcpScreenState extends State { void loadDhcpStatus() async { final result = await Provider.of(context, listen: false).loadDhcpStatus(); - if (mounted && result == true) { - final dhcpProvider = Provider.of(context, listen: false); - if (dhcpProvider.dhcp != null) { - setState(() { - if (dhcpProvider.dhcp!.dhcpStatus.interfaceName != null && dhcpProvider.dhcp!.dhcpStatus.interfaceName != '') { - try {selectedInterface = dhcpProvider.dhcp!.networkInterfaces.firstWhere((iface) => iface.name == dhcpProvider.dhcp!.dhcpStatus.interfaceName);} catch (_) {} - enabled = dhcpProvider.dhcp!.dhcpStatus.enabled; - if (dhcpProvider.dhcp!.dhcpStatus.v4 != null) { - ipv4StartRangeController.text = dhcpProvider.dhcp!.dhcpStatus.v4!.rangeStart; - ipv4EndRangeController.text = dhcpProvider.dhcp!.dhcpStatus.v4!.rangeEnd ?? ''; - ipv4SubnetMaskController.text = dhcpProvider.dhcp!.dhcpStatus.v4!.subnetMask ?? ''; - ipv4GatewayController.text = dhcpProvider.dhcp!.dhcpStatus.v4!.gatewayIp ?? ''; - ipv4LeaseTimeController.text = dhcpProvider.dhcp!.dhcpStatus.v4!.leaseDuration.toString(); - } - } - }); + if (!mounted || result == false) return; + + final dhcpProvider = Provider.of(context, listen: false); + if (dhcpProvider.dhcp == null) return; + + setState(() { + if (dhcpProvider.dhcp!.dhcpStatus!.interfaceName != null && dhcpProvider.dhcp!.dhcpStatus!.interfaceName != '') { + try {selectedInterface = dhcpProvider.dhcp!.networkInterfaces.firstWhere((iface) => iface.name == dhcpProvider.dhcp!.dhcpStatus!.interfaceName);} catch (_) {} + enabled = dhcpProvider.dhcp!.dhcpStatus!.enabled; + if (dhcpProvider.dhcp!.dhcpStatus!.v4 != null) { + ipv4StartRangeController.text = dhcpProvider.dhcp!.dhcpStatus!.v4!.rangeStart; + ipv4EndRangeController.text = dhcpProvider.dhcp!.dhcpStatus!.v4!.rangeEnd ?? ''; + ipv4SubnetMaskController.text = dhcpProvider.dhcp!.dhcpStatus!.v4!.subnetMask ?? ''; + ipv4GatewayController.text = dhcpProvider.dhcp!.dhcpStatus!.v4!.gatewayIp ?? ''; + ipv4LeaseTimeController.text = dhcpProvider.dhcp!.dhcpStatus!.v4!.leaseDuration.toString(); + } } - } + }); + checkDataValid(); } @@ -266,8 +269,8 @@ class _DhcpScreenState extends State { if (result.successful == true) { DhcpModel data = dhcpProvider.dhcp!; - data.dhcpStatus.staticLeases = []; - data.dhcpStatus.leases = []; + data.dhcpStatus!.staticLeases = []; + data.dhcpStatus!.leases = []; dhcpProvider.setDhcpData(data); showSnacbkar( @@ -350,6 +353,14 @@ class _DhcpScreenState extends State { }); } + if ( + dhcpProvider.loadStatus == LoadStatus.loaded && + dhcpProvider.dhcp != null && + dhcpProvider.dhcp!.dhcpAvailable == false + ) { + return const DhcpNotAvailable(); + } + return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.dhcpSettings), @@ -429,60 +440,10 @@ class _DhcpScreenState extends State { return SingleChildScrollView( child: Wrap( children: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - left: 16, - right: 16 - ), - child: Material( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(28), - child: InkWell( - onTap: selectedInterface != null - ? () => setState(() => enabled = !enabled) - : null, - borderRadius: BorderRadius.circular(28), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 12 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.enableDhcpServer, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - if (selectedInterface != null) ...[ - Text( - selectedInterface!.name, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).listTileTheme.textColor - ), - ) - ] - ], - ), - Switch( - value: enabled, - onChanged: selectedInterface != null - ? (value) => setState(() => enabled = value) - : null, - ), - ], - ), - ), - ), - ), + DhcpMainButton( + selectedInterface: selectedInterface, + enabled: enabled, + setEnabled: (v) => setState(() => enabled = v) ), if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[ SectionLabel( @@ -491,125 +452,47 @@ class _DhcpScreenState extends State { top: 24, left: 16, right: 16, bottom: 8 ) ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv4StartRangeController, - onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.skip_previous_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv4StartRangeError, - labelText: AppLocalizations.of(context)!.startOfRange, - ), - keyboardType: TextInputType.number, - ), - ), + _DhcpField( + icon: Icons.skip_previous_rounded, + label: AppLocalizations.of(context)!.startOfRange, + controller: ipv4StartRangeController, + onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid), + error: ipv4StartRangeError ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv4EndRangeController, - onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.skip_next_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv4EndRangeError, - labelText: AppLocalizations.of(context)!.endOfRange, - ), - keyboardType: TextInputType.number, - ), - ), + _DhcpField( + icon: Icons.skip_next_rounded, + label: AppLocalizations.of(context)!.endOfRange, + controller: ipv4EndRangeController, + onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid), + error: ipv4EndRangeError ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv4SubnetMaskController, - onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.hub_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv4SubnetMaskError, - labelText: AppLocalizations.of(context)!.subnetMask, - ), - keyboardType: TextInputType.number, - ), - ), + _DhcpField( + icon: Icons.hub_rounded, + label: AppLocalizations.of(context)!.subnetMask, + controller: ipv4SubnetMaskController, + onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid), + error: ipv4SubnetMaskError ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv4GatewayController, - onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.router_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv4GatewayError, - labelText: AppLocalizations.of(context)!.gateway, - ), - keyboardType: TextInputType.number, - ), - ), + _DhcpField( + icon: Icons.router_rounded, + label: AppLocalizations.of(context)!.gateway, + controller: ipv4GatewayController, + onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid), + error: ipv4GatewayError ), - FractionallySizedBox( - widthFactor: 1, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv4LeaseTimeController, - onChanged: (value) { - if (int.tryParse(value).runtimeType == int) { - setState(() => ipv4LeaseTimeError = null); - } - else { - setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); - } - }, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.timer), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv4LeaseTimeError, - labelText: AppLocalizations.of(context)!.leaseTime, - ), - keyboardType: TextInputType.number, - ), - ), + _DhcpField( + icon: Icons.timer, + label: AppLocalizations.of(context)!.leaseTime, + controller: ipv4LeaseTimeController, + onChanged: (value) { + if (int.tryParse(value).runtimeType == int) { + setState(() => ipv4LeaseTimeError = null); + } + else { + setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); + } + }, + error: ipv4LeaseTimeError ), ], if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[ @@ -617,80 +500,34 @@ class _DhcpScreenState extends State { label: AppLocalizations.of(context)!.ipv6settings, padding: const EdgeInsets.all(16) ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 8, bottom: 12, left: 16, right: 8) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv6StartRangeController, - onChanged: (value) => validateIpV4(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.skip_next_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv6StartRangeError, - labelText: AppLocalizations.of(context)!.startOfRange, - ), - keyboardType: TextInputType.number, - ), - ), + _DhcpField( + icon: Icons.skip_next_rounded, + label: AppLocalizations.of(context)!.startOfRange, + controller: ipv6StartRangeController, + onChanged: (value) => validateIpV6(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid), + error: ipv6StartRangeError ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 8, bottom: 12, left: 8, right: 16) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv6EndRangeController, - onChanged: (value) => validateIpV4(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.skip_previous_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv6EndRangeError, - labelText: AppLocalizations.of(context)!.endOfRange, - ), - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: 1, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv6LeaseTimeController, - onChanged: (value) { - if (int.tryParse(value).runtimeType == int) { - setState(() => ipv6LeaseTimeError = null); - } - else { - setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); - } - }, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.timer), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv6LeaseTimeError, - labelText: AppLocalizations.of(context)!.leaseTime, - ), - keyboardType: TextInputType.number, - ), - ), + _DhcpField( + icon: Icons.skip_previous_rounded, + label: AppLocalizations.of(context)!.endOfRange, + controller: ipv6EndRangeController, + onChanged: (value) => validateIpV6(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid), + error: ipv6EndRangeError ), + _DhcpField( + icon: Icons.timer, + label: AppLocalizations.of(context)!.leaseTime, + controller: ipv6LeaseTimeController, + onChanged: (value) { + if (int.tryParse(value).runtimeType == int) { + setState(() => ipv6LeaseTimeError = null); + } + else { + setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); + } + }, + error: ipv6LeaseTimeError + ) ], const SizedBox(height: 20), SectionLabel( @@ -704,7 +541,7 @@ class _DhcpScreenState extends State { Navigator.of(context).push( MaterialPageRoute( builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.leases, + items: dhcpProvider.dhcp!.dhcpStatus!.leases, staticLeases: false, ) ) @@ -739,7 +576,7 @@ class _DhcpScreenState extends State { Navigator.of(context).push( MaterialPageRoute( builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, + items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases, staticLeases: true, ) ) @@ -775,7 +612,7 @@ class _DhcpScreenState extends State { if (!(Platform.isAndroid || Platform.isIOS)) { SplitView.of(context).push( DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.leases, + items: dhcpProvider.dhcp!.dhcpStatus!.leases, staticLeases: false, ) ); @@ -784,7 +621,7 @@ class _DhcpScreenState extends State { Navigator.of(context).push( MaterialPageRoute( builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.leases, + items: dhcpProvider.dhcp!.dhcpStatus!.leases, staticLeases: false, ) ) @@ -804,7 +641,7 @@ class _DhcpScreenState extends State { if (!(Platform.isAndroid || Platform.isIOS)) { SplitView.of(context).push( DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, + items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases, staticLeases: true, ) ); @@ -813,7 +650,7 @@ class _DhcpScreenState extends State { Navigator.of(context).push( MaterialPageRoute( builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, + items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases, staticLeases: true, ) ) @@ -899,4 +736,49 @@ class _DhcpScreenState extends State { ) ); } +} + +class _DhcpField extends StatelessWidget { + final IconData icon; + final String label; + final TextEditingController controller; + final void Function(String) onChanged; + final String? error; + + const _DhcpField({ + required this.icon, + required this.label, + required this.controller, + required this.onChanged, + required this.error, + }); + + @override + Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + + return FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: controller, + onChanged: onChanged, + decoration: InputDecoration( + prefixIcon: Icon(icon), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: error, + labelText: label, + ), + keyboardType: TextInputType.number, + ), + ), + ); + } } \ No newline at end of file diff --git a/lib/screens/settings/dhcp/dhcp_main_button.dart b/lib/screens/settings/dhcp/dhcp_main_button.dart new file mode 100644 index 0000000..3e46a14 --- /dev/null +++ b/lib/screens/settings/dhcp/dhcp_main_button.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/models/dhcp.dart'; + +class DhcpMainButton extends StatelessWidget { + final NetworkInterface? selectedInterface; + final bool enabled; + final void Function(bool) setEnabled; + + const DhcpMainButton({ + super.key, + required this.selectedInterface, + required this.enabled, + required this.setEnabled, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only( + top: 10, + left: 16, + right: 16 + ), + child: Material( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(28), + child: InkWell( + onTap: selectedInterface != null + ? () => setEnabled(!enabled) + : null, + borderRadius: BorderRadius.circular(28), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 12 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.enableDhcpServer, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + if (selectedInterface != null) ...[ + Text( + selectedInterface!.name, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).listTileTheme.textColor + ), + ) + ] + ], + ), + Switch( + value: enabled, + onChanged: selectedInterface != null + ? (value) => setEnabled(value) + : null, + ), + ], + ), + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/dhcp/dhcp_not_available.dart b/lib/screens/settings/dhcp/dhcp_not_available.dart new file mode 100644 index 0000000..ceb3f6b --- /dev/null +++ b/lib/screens/settings/dhcp/dhcp_not_available.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/functions/desktop_mode.dart'; + +class DhcpNotAvailable extends StatelessWidget { + const DhcpNotAvailable({super.key}); + + @override + Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.dhcpSettings), + centerTitle: false, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + AppLocalizations.of(context)!.dhcpNotAvailable, + textAlign: TextAlign.center, + style: const TextStyle( + fontWeight: FontWeight.w400, + fontSize: 24 + ), + ), + const SizedBox(height: 20), + Text( + AppLocalizations.of(context)!.osServerInstalledIncompatible, + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index 636cc34..f626f75 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -482,8 +482,13 @@ class ApiClientV2 { return ApiResponse( successful: true, content: DhcpModel( + dhcpAvailable: jsonDecode(results[1].body!)['message'] != null + ? false + : true, networkInterfaces: interfaces, - dhcpStatus: DhcpStatus.fromJson(jsonDecode(results[1].body!)) + dhcpStatus: jsonDecode(results[1].body!)['message'] != null + ? null + : DhcpStatus.fromJson(jsonDecode(results[1].body!)) ) ); } catch (e, stackTrace) { diff --git a/lib/services/http_requests.dart b/lib/services/http_requests.dart index 2d001f2..37a2cc8 100644 --- a/lib/services/http_requests.dart +++ b/lib/services/http_requests.dart @@ -1352,6 +1352,7 @@ class ApiClient { return { 'result': 'success', 'data': DhcpModel( + dhcpAvailable: true, networkInterfaces: interfaces, dhcpStatus: DhcpStatus.fromJson(jsonDecode(result[1]['body'])) ) From b164d520db51ea98915978c344462f836c7afe1f Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 9 Dec 2023 04:04:14 +0100 Subject: [PATCH 145/177] Changed system navigation bar color --- lib/main.dart | 2 + lib/screens/clients/client/client_screen.dart | 58 +- .../clients/client/logs_list_client.dart | 218 +++--- lib/screens/connect/connect.dart | 44 +- .../filters/details/add_list_modal.dart | 333 +++++---- .../filters/details/check_host_modal.dart | 352 ++++----- .../filters/details/list_details_screen.dart | 72 +- .../filters/modals/add_custom_rule.dart | 10 +- .../modals/blocked_services_screen.dart | 249 ++++--- .../filters/modals/delete_list_modal.dart | 4 +- .../modals/update_interval_lists_modal.dart | 10 +- .../filters/selection/selection_lists.dart | 5 +- .../management_modal/management_modal.dart | 88 +-- .../home/top_items/top_items_screen.dart | 18 +- .../logs/configuration/logs_config_modal.dart | 62 +- .../logs/details/log_details_screen.dart | 5 +- lib/screens/logs/filters/clients_modal.dart | 8 +- .../logs/filters/filter_status_modal.dart | 10 +- .../logs/filters/logs_filters_modal.dart | 12 +- lib/screens/servers/servers.dart | 50 +- .../access_settings/add_client_modal.dart | 8 +- lib/screens/settings/advanced_setings.dart | 48 +- .../settings/customization/customization.dart | 286 ++++---- .../settings/dhcp/add_static_lease_modal.dart | 335 ++++----- lib/screens/settings/dhcp/dhcp.dart | 624 ++++++++-------- lib/screens/settings/dhcp/dhcp_leases.dart | 56 +- .../settings/dhcp/select_interface_modal.dart | 17 +- lib/screens/settings/dns/bootstrap_dns.dart | 182 ++--- lib/screens/settings/dns/cache_config.dart | 146 ++-- lib/screens/settings/dns/comment_modal.dart | 259 ++++--- lib/screens/settings/dns/dns.dart | 178 ++--- .../settings/dns/dns_server_settings.dart | 242 ++++--- .../settings/dns/private_reverse_servers.dart | 258 +++---- lib/screens/settings/dns/upstream_dns.dart | 234 +++--- .../dns_rewrites/dns_rewrite_modal.dart | 10 +- .../settings/dns_rewrites/dns_rewrites.dart | 444 ++++++------ .../settings/dns_rewrites/rule_modal.dart | 0 .../dns_rewrites/server_version_needed.dart | 1 + .../settings/encryption/encryption.dart | 682 +++++++++--------- .../general_settings/general_settings.dart | 298 ++++---- .../settings/safe_search_settings.dart | 296 ++++---- .../settings/server_info/server_info.dart | 204 +++--- lib/screens/settings/theme_modal.dart | 4 +- .../settings/update_server/update.dart | 55 +- lib/widgets/add_server/add_server_modal.dart | 6 +- lib/widgets/system_ui_overlay_style.dart | 14 +- lib/widgets/tab_content_list.dart | 80 +- 47 files changed, 3370 insertions(+), 3207 deletions(-) delete mode 100644 lib/screens/settings/dns_rewrites/rule_modal.dart diff --git a/lib/main.dart b/lib/main.dart index 9734263..33f241d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.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'; @@ -35,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)); diff --git a/lib/screens/clients/client/client_screen.dart b/lib/screens/clients/client/client_screen.dart index 2ab5c75..d4628e7 100644 --- a/lib/screens/clients/client/client_screen.dart +++ b/lib/screens/clients/client/client_screen.dart @@ -185,34 +185,36 @@ class _ClientScreenState extends State { ), actions: actions(), ), - body: ClientForm( - isFullScreen: true, - client: widget.client, - nameController: nameController, - updateValidValues: (v) => setState(() => validValues = v), - 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), + body: SafeArea( + child: ClientForm( + isFullScreen: true, + client: widget.client, + nameController: nameController, + updateValidValues: (v) => setState(() => validValues = v), + 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), + ), ), ), ); diff --git a/lib/screens/clients/client/logs_list_client.dart b/lib/screens/clients/client/logs_list_client.dart index bd4d2af..3267128 100644 --- a/lib/screens/clients/client/logs_list_client.dart +++ b/lib/screens/clients/client/logs_list_client.dart @@ -149,125 +149,127 @@ class _LogsListClientState extends State { ] ], ), - body: 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( + 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: false + dialog: true ) ) - ) - } - }, - twoColumns: widget.splitView, - ); + } + 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, + ); + } + else { + return Center( + child: Text( + AppLocalizations.of(context)!.noLogsDisplay, textAlign: TextAlign.center, style: TextStyle( fontSize: 22, color: Theme.of(context).colorScheme.onSurfaceVariant, ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - }, + ), + ); + } + + 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(); + } + }, + ), ) ); } diff --git a/lib/screens/connect/connect.dart b/lib/screens/connect/connect.dart index 3528718..01ac7cc 100644 --- a/lib/screens/connect/connect.dart +++ b/lib/screens/connect/connect.dart @@ -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 createState() => _ConnectState(); @@ -61,26 +61,28 @@ class _ConnectState extends State { 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() + ) + ], + ), ), ); } diff --git a/lib/screens/filters/details/add_list_modal.dart b/lib/screens/filters/details/add_list_modal.dart index fceebf9..d5579a6 100644 --- a/lib/screens/filters/details/add_list_modal.dart +++ b/lib/screens/filters/details/add_list_modal.dart @@ -5,7 +5,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/models/filtering.dart'; -class AddListModal extends StatefulWidget { +class AddListModal extends StatelessWidget { final String type; final Filter? list; final void Function({required String name, required String url, required String type})? onConfirm; @@ -13,19 +13,74 @@ class AddListModal extends StatefulWidget { final bool dialog; const AddListModal({ - Key? key, + super.key, required this.type, this.list, this.onConfirm, this.onEdit, required this.dialog - }) : super(key: key); + }); @override - State createState() => _AddListModalState(); + Widget build(BuildContext context) { + if (dialog == true) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400 + ), + child: _Content( + list: list, + onConfirm: onConfirm, + onEdit: onEdit, + type: type, + ) + ), + ); + } + else { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) + ), + color: Theme.of(context).dialogBackgroundColor + ), + child: SafeArea( + child: _Content( + list: list, + onConfirm: onConfirm, + onEdit: onEdit, + type: type, + ), + ) + ), + ); + } + } } -class _AddListModalState extends State { +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; @@ -69,162 +124,134 @@ class _AddListModalState extends State { } @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, + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SingleChildScrollView( + child: Wrap( children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) + 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), + ], + ), + ], ), - 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 - ) + 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, + ), + ), ), ], ), ), - 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 + 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 + ) + ), + ], ), - child: content() ), - ); - } + if (Platform.isIOS) const SizedBox(height: 16) + ], + ); } } \ No newline at end of file diff --git a/lib/screens/filters/details/check_host_modal.dart b/lib/screens/filters/details/check_host_modal.dart index 5325ada..3065588 100644 --- a/lib/screens/filters/details/check_host_modal.dart +++ b/lib/screens/filters/details/check_host_modal.dart @@ -8,19 +8,55 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/functions/get_filtered_status.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; -class CheckHostModal extends StatefulWidget { +class CheckHostModal extends StatelessWidget { final bool dialog; const CheckHostModal({ - Key? key, + super.key, required this.dialog - }) : super(key: key); + }); @override - State createState() => _CheckHostModalState(); + Widget build(BuildContext context) { + if (dialog == true) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400 + ), + child: const _Content() + ), + ); + } + else { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: Container( + width: double.maxFinite, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28), + ), + color: Theme.of(context).dialogBackgroundColor + ), + child: const SafeArea( + child: _Content() + ) + ), + ); + } + } } -class _CheckHostModalState extends State { +class _Content extends StatefulWidget { + const _Content(); + + @override + State<_Content> createState() => _ContentState(); +} + +class _ContentState extends State<_Content> { final TextEditingController domainController = TextEditingController(); String? domainError; @@ -59,17 +95,29 @@ class _CheckHostModalState extends State { setState(() => resultWidget = checking()); final result = await serversProvider.apiClient2!.checkHostFiltered(host: domainController.text); + if (!mounted) return; - if (mounted) { - 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, + 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 @@ -77,39 +125,6 @@ class _CheckHostModalState extends State { : 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 ), ) @@ -117,143 +132,134 @@ class _CheckHostModalState extends State { )); } } + 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 - ), + 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, + 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, ), ), ), - if (resultWidget != null) Padding( - padding: const EdgeInsets.all(24), - child: resultWidget, + ), + ], + ), + ), + ), + Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.only( + bottom: 24, + right: 24 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.close), ), - if (resultWidget == null) Padding( - padding: const EdgeInsets.all(24), - child: Center( - child: Text( - AppLocalizations.of(context)!.insertDomain, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 16, - ), + 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 ), ), ), ], ), - ), - ), - 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() - ), - ); - } + ) + ], + ) + ], + ); } } \ No newline at end of file diff --git a/lib/screens/filters/details/list_details_screen.dart b/lib/screens/filters/details/list_details_screen.dart index b5e305c..bbc7c41 100644 --- a/lib/screens/filters/details/list_details_screen.dart +++ b/lib/screens/filters/details/list_details_screen.dart @@ -25,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 createState() => _ListDetailsScreenState(); @@ -367,42 +367,44 @@ class _ListDetailsScreenState extends State { title: Text(AppLocalizations.of(context)!.listDetails), actions: list != null ? actions() : null, ), - body: Stack( - children: [ - if (list != null) ListView( - children: content(), - ), - if (list == null) Center( - child: Text( - AppLocalizations.of(context)!.listNotAvailable, - style: const TextStyle( - fontSize: 24, + 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 + 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, + ), ), - child: Icon( - list.enabled == true - ? Icons.gpp_bad_rounded - : Icons.verified_user_rounded, - ), - ), - ) - ], + ) + ], + ), ), ), ); diff --git a/lib/screens/filters/modals/add_custom_rule.dart b/lib/screens/filters/modals/add_custom_rule.dart index c7e5bcd..abe68bc 100644 --- a/lib/screens/filters/modals/add_custom_rule.dart +++ b/lib/screens/filters/modals/add_custom_rule.dart @@ -10,10 +10,10 @@ class AddCustomRule extends StatefulWidget { final bool fullScreen; const AddCustomRule({ - Key? key, + super.key, required this.onConfirm, required this.fullScreen - }) : super(key: key); + }); @override State createState() => _AddCustomRuleState(); @@ -349,8 +349,10 @@ class _AddCustomRuleState extends State { const SizedBox(width: 10) ], ), - body: ListView( - children: content(), + body: SafeArea( + child: ListView( + children: content(), + ), ) ), ); diff --git a/lib/screens/filters/modals/blocked_services_screen.dart b/lib/screens/filters/modals/blocked_services_screen.dart index 6169d0b..b95dd4f 100644 --- a/lib/screens/filters/modals/blocked_services_screen.dart +++ b/lib/screens/filters/modals/blocked_services_screen.dart @@ -17,9 +17,9 @@ class BlockedServicesScreen extends StatefulWidget { final bool fullScreen; const BlockedServicesScreen({ - Key? key, + super.key, required this.fullScreen - }) : super(key: key); + }); @override State createState() => _BlockedServicesScreenStateWidget(); @@ -83,105 +83,6 @@ class _BlockedServicesScreenStateWidget extends State { } } - Widget body() { - switch (filteringProvider.blockedServicesLoadStatus) { - case LoadStatus.loading: - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16), - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingBlockedServicesList, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - return ListView.builder( - itemCount: filteringProvider.blockedServices!.services.length, - itemBuilder: (context, index) => Material( - color: Colors.transparent, - child: InkWell( - onTap: () => updateValues( - values.contains(filteringProvider.blockedServices!.services[index].id), - filteringProvider.blockedServices!.services[index] - ), - child: Padding( - padding: const EdgeInsets.only( - top: 6, - bottom: 6, - right: 12, - left: 24 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - filteringProvider.blockedServices!.services[index].name, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Checkbox( - value: values.contains(filteringProvider.blockedServices!.services[index].id), - onChanged: (value) => updateValues( - value!, - filteringProvider.blockedServices!.services[index] - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5) - ), - ) - ], - ), - ), - ), - ) - ); - - case LoadStatus.error: - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16), - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.blockedServicesListNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - } - if (widget.fullScreen == true) { return Dialog.fullscreen( child: Scaffold( @@ -199,18 +100,23 @@ class _BlockedServicesScreenStateWidget extends State { 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() + 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, + ) + ), ), ), ); @@ -256,7 +162,10 @@ class _BlockedServicesScreenStateWidget extends State { ), ), Expanded( - child: body() + child: _Content( + values: values, + updateValues: updateValues, + ) ), ], ) @@ -266,6 +175,118 @@ class _BlockedServicesScreenStateWidget extends State { } } +class _Content extends StatelessWidget { + final List 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(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, diff --git a/lib/screens/filters/modals/delete_list_modal.dart b/lib/screens/filters/modals/delete_list_modal.dart index 5d751e4..86e7e88 100644 --- a/lib/screens/filters/modals/delete_list_modal.dart +++ b/lib/screens/filters/modals/delete_list_modal.dart @@ -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) { diff --git a/lib/screens/filters/modals/update_interval_lists_modal.dart b/lib/screens/filters/modals/update_interval_lists_modal.dart index 4827082..f6504a5 100644 --- a/lib/screens/filters/modals/update_interval_lists_modal.dart +++ b/lib/screens/filters/modals/update_interval_lists_modal.dart @@ -66,10 +66,12 @@ class _UpdateIntervalListsModalState extends State { topRight: Radius.circular(28) ), ), - child: _Content( - selectedOption: selectedOption, - onUpdateValue: _updateRadioValue, - onConfirm: () => widget.onChange(selectedOption!), + child: SafeArea( + child: _Content( + selectedOption: selectedOption, + onUpdateValue: _updateRadioValue, + onConfirm: () => widget.onChange(selectedOption!), + ), ) ), ); diff --git a/lib/screens/filters/selection/selection_lists.dart b/lib/screens/filters/selection/selection_lists.dart index 9562e74..b423f77 100644 --- a/lib/screens/filters/selection/selection_lists.dart +++ b/lib/screens/filters/selection/selection_lists.dart @@ -72,19 +72,18 @@ class SelectionSliverList extends StatelessWidget { final void Function() unselectAll; const SelectionSliverList({ - Key? key, + super.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 SafeArea( top: false, - bottom: false, child: Builder( builder: (BuildContext context) { return CustomScrollView( diff --git a/lib/screens/home/management_modal/management_modal.dart b/lib/screens/home/management_modal/management_modal.dart index 79fe043..b18586b 100644 --- a/lib/screens/home/management_modal/management_modal.dart +++ b/lib/screens/home/management_modal/management_modal.dart @@ -19,9 +19,9 @@ class ManagementModal extends StatefulWidget { final bool dialog; const ManagementModal({ - Key? key, + super.key, required this.dialog - }) : super(key: key); + }); @override State createState() => _ManagementModalState(); @@ -141,33 +141,35 @@ class _ManagementModalState extends State with SingleTickerProv topRight: Radius.circular(28) ) ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: SingleChildScrollView( - child: _Modal( - expandableController: expandableController, - updateBlocking: updateBlocking, - disableWithCountdown: disableWithCountdown, - animation: animation, - ) + 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), - ), - ], + 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) - ], + if (Platform.isIOS) const SizedBox(height: 16) + ], + ), ), ); } @@ -201,24 +203,24 @@ class _Modal extends StatelessWidget { 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, + 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, + ), ), ), - ), ], ), ], diff --git a/lib/screens/home/top_items/top_items_screen.dart b/lib/screens/home/top_items/top_items_screen.dart index 01ac83e..7e7e623 100644 --- a/lib/screens/home/top_items/top_items_screen.dart +++ b/lib/screens/home/top_items/top_items_screen.dart @@ -122,14 +122,16 @@ class _TopItemsScreenState extends State { const SizedBox(width: 8) ], ), - body: _Content( - buildValue: widget.buildValue, - isClient: widget.isClient, - onTapEntry: widget.onTapEntry, - options: widget.options, - screenData: screenData, - total: total, - withProgressBar: widget.withProgressBar, + body: SafeArea( + child: _Content( + buildValue: widget.buildValue, + isClient: widget.isClient, + onTapEntry: widget.onTapEntry, + options: widget.options, + screenData: screenData, + total: total, + withProgressBar: widget.withProgressBar, + ), ), ), ); diff --git a/lib/screens/logs/configuration/logs_config_modal.dart b/lib/screens/logs/configuration/logs_config_modal.dart index 6352f36..5c2ba44 100644 --- a/lib/screens/logs/configuration/logs_config_modal.dart +++ b/lib/screens/logs/configuration/logs_config_modal.dart @@ -148,36 +148,38 @@ class _LogsConfigModalState extends State { ), color: Theme.of(context).dialogBackgroundColor ), - child: Builder( - builder: (context) { - switch (loadStatus) { - case LoadStatus.loading: - return const ConfigLogsLoading(); - - case LoadStatus.loaded: - return LogsConfigOptions( - generalSwitch: generalSwitch, - updateGeneralSwitch: (v) => setState(() => generalSwitch = v), - anonymizeClientIp: anonymizeClientIp, - updateAnonymizeClientIp: (v) => setState(() => anonymizeClientIp = v), - retentionItems: retentionItems, - retentionTime: retentionTime, - updateRetentionTime: (v) => setState(() => retentionTime = v), - onClear: () => widget.onClear(), - onConfirm: () => widget.onConfirm({ - "enabled": generalSwitch, - "interval": retentionTime, - "anonymize_client_ip": anonymizeClientIp - }) - ); - - case LoadStatus.error: - return const ConfigLogsError(); - - default: - return const SizedBox(); - } - }, + child: SafeArea( + child: Builder( + builder: (context) { + switch (loadStatus) { + case LoadStatus.loading: + return const ConfigLogsLoading(); + + case LoadStatus.loaded: + return LogsConfigOptions( + generalSwitch: generalSwitch, + updateGeneralSwitch: (v) => setState(() => generalSwitch = v), + anonymizeClientIp: anonymizeClientIp, + updateAnonymizeClientIp: (v) => setState(() => anonymizeClientIp = v), + retentionItems: retentionItems, + retentionTime: retentionTime, + updateRetentionTime: (v) => setState(() => retentionTime = v), + onClear: () => widget.onClear(), + onConfirm: () => widget.onConfirm({ + "enabled": generalSwitch, + "interval": retentionTime, + "anonymize_client_ip": anonymizeClientIp + }) + ); + + case LoadStatus.error: + return const ConfigLogsError(); + + default: + return const SizedBox(); + } + }, + ), ) ); } diff --git a/lib/screens/logs/details/log_details_screen.dart b/lib/screens/logs/details/log_details_screen.dart index ff88eaa..42918dd 100644 --- a/lib/screens/logs/details/log_details_screen.dart +++ b/lib/screens/logs/details/log_details_screen.dart @@ -25,10 +25,10 @@ class LogDetailsScreen extends StatelessWidget { final bool dialog; const LogDetailsScreen({ - Key? key, + super.key, required this.log, required this.dialog - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -322,7 +322,6 @@ class LogDetailsScreen extends StatelessWidget { ], body: SafeArea( top: false, - bottom: false, child: Builder( builder: (context) => CustomScrollView( slivers: [ diff --git a/lib/screens/logs/filters/clients_modal.dart b/lib/screens/logs/filters/clients_modal.dart index 1730d8e..84728f8 100644 --- a/lib/screens/logs/filters/clients_modal.dart +++ b/lib/screens/logs/filters/clients_modal.dart @@ -60,9 +60,11 @@ class _ClientsModalState extends State { ), color: Theme.of(context).dialogBackgroundColor ), - child: _ModalContent( - selectedClients: selectedClients, - onClientsSelected: (v) => setState(() => selectedClients = v), + child: SafeArea( + child: _ModalContent( + selectedClients: selectedClients, + onClientsSelected: (v) => setState(() => selectedClients = v), + ), ) ), ); diff --git a/lib/screens/logs/filters/filter_status_modal.dart b/lib/screens/logs/filters/filter_status_modal.dart index d3c1079..88f782b 100644 --- a/lib/screens/logs/filters/filter_status_modal.dart +++ b/lib/screens/logs/filters/filter_status_modal.dart @@ -62,10 +62,12 @@ class _FilterStatusModalState extends State { ), color: Theme.of(context).dialogBackgroundColor ), - child: _Content( - onApply: apply, - updateSelectedResultStatus: (v) => setState(() => selectedResultStatus = v), - selectedResultStatus: selectedResultStatus, + child: SafeArea( + child: _Content( + onApply: apply, + updateSelectedResultStatus: (v) => setState(() => selectedResultStatus = v), + selectedResultStatus: selectedResultStatus, + ), ) ); } diff --git a/lib/screens/logs/filters/logs_filters_modal.dart b/lib/screens/logs/filters/logs_filters_modal.dart index 8e1cdbe..5e7e746 100644 --- a/lib/screens/logs/filters/logs_filters_modal.dart +++ b/lib/screens/logs/filters/logs_filters_modal.dart @@ -19,9 +19,9 @@ class LogsFiltersModal extends StatefulWidget { final bool dialog; const LogsFiltersModal({ - Key? key, + super.key, required this.dialog - }) : super(key: key); + }); @override State createState() => _LogsFiltersModalState(); @@ -65,9 +65,11 @@ class _LogsFiltersModalState extends State { topRight: Radius.circular(28) ) ), - child: _FiltersList( - searchController: searchController, - onClearSearch: () => setState(() => searchController.text = "") + child: SafeArea( + child: _FiltersList( + searchController: searchController, + onClearSearch: () => setState(() => searchController.text = "") + ), ) ), ); diff --git a/lib/screens/servers/servers.dart b/lib/screens/servers/servers.dart index 180af77..25bca06 100644 --- a/lib/screens/servers/servers.dart +++ b/lib/screens/servers/servers.dart @@ -16,9 +16,9 @@ class Servers extends StatefulWidget { final double? breakingWidth; const Servers({ - Key? key, + super.key, this.breakingWidth - }) : super(key: key); + }); @override State createState() => _ServersState(); @@ -77,29 +77,31 @@ class _ServersState extends State { title: Text(AppLocalizations.of(context)!.servers), centerTitle: false, ), - body: Stack( - children: [ - ServersList( - context: context, - controllers: expandableControllerList, - onChange: expandOrContract, - scrollController: scrollController, - breakingWidth: widget.breakingWidth ?? 700, - ), - AnimatedPositioned( - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - bottom: isVisible ? - appConfigProvider.showingSnackbar - ? 70 : (Platform.isIOS ? 40 : 20) - : -70, - right: 20, - child: FloatingActionButton( - onPressed: openAddServerModal, - child: const Icon(Icons.add), + body: SafeArea( + child: Stack( + children: [ + ServersList( + context: context, + controllers: expandableControllerList, + onChange: expandOrContract, + scrollController: scrollController, + breakingWidth: widget.breakingWidth ?? 700, ), - ), - ], + AnimatedPositioned( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + bottom: isVisible ? + appConfigProvider.showingSnackbar + ? 70 : (Platform.isIOS ? 40 : 20) + : -70, + right: 20, + child: FloatingActionButton( + onPressed: openAddServerModal, + child: const Icon(Icons.add), + ), + ), + ], + ), ), ); } diff --git a/lib/screens/settings/access_settings/add_client_modal.dart b/lib/screens/settings/access_settings/add_client_modal.dart index 014babd..bb0385a 100644 --- a/lib/screens/settings/access_settings/add_client_modal.dart +++ b/lib/screens/settings/access_settings/add_client_modal.dart @@ -44,9 +44,11 @@ class AddClientModal extends StatelessWidget { topRight: Radius.circular(28) ) ), - child: _Content( - type: type, - onConfirm: onConfirm, + child: SafeArea( + child: _Content( + type: type, + onConfirm: onConfirm, + ), ) ), ); diff --git a/lib/screens/settings/advanced_setings.dart b/lib/screens/settings/advanced_setings.dart index 39a93e3..9483cd8 100644 --- a/lib/screens/settings/advanced_setings.dart +++ b/lib/screens/settings/advanced_setings.dart @@ -11,7 +11,7 @@ import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class AdvancedSettings extends StatelessWidget { - const AdvancedSettings({Key? key}) : super(key: key); + const AdvancedSettings({super.key}); @override Widget build(BuildContext context) { @@ -46,31 +46,33 @@ class AdvancedSettings extends StatelessWidget { title: Text(AppLocalizations.of(context)!.advancedSettings), surfaceTintColor: isDesktop(width) ? Colors.transparent : null, ), - body: ListView( - children: [ - CustomListTile( - icon: Icons.lock, - title: AppLocalizations.of(context)!.dontCheckCertificate, - subtitle: AppLocalizations.of(context)!.dontCheckCertificateDescription, - trailing: Switch( - value: appConfigProvider.overrideSslCheck, - onChanged: (value) => updateSettings( - newStatus: value, + body: SafeArea( + child: ListView( + children: [ + CustomListTile( + icon: Icons.lock, + title: AppLocalizations.of(context)!.dontCheckCertificate, + subtitle: AppLocalizations.of(context)!.dontCheckCertificateDescription, + trailing: Switch( + value: appConfigProvider.overrideSslCheck, + onChanged: (value) => updateSettings( + newStatus: value, + function: appConfigProvider.setOverrideSslCheck + ), + ), + onTap: () => updateSettings( + newStatus: !appConfigProvider.overrideSslCheck, function: appConfigProvider.setOverrideSslCheck ), + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 20, + right: 10 + ) ), - onTap: () => updateSettings( - newStatus: !appConfigProvider.overrideSslCheck, - function: appConfigProvider.setOverrideSslCheck - ), - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - left: 20, - right: 10 - ) - ), - ], + ], + ), ) ); } diff --git a/lib/screens/settings/customization/customization.dart b/lib/screens/settings/customization/customization.dart index 6ddd5e4..fb4526b 100644 --- a/lib/screens/settings/customization/customization.dart +++ b/lib/screens/settings/customization/customization.dart @@ -14,7 +14,7 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/constants/colors.dart'; class Customization extends StatelessWidget { - const Customization({Key? key}) : super(key: key); + const Customization({super.key}); @override Widget build(BuildContext context) { @@ -65,152 +65,154 @@ class _CustomizationWidgetState extends State { centerTitle: false, surfaceTintColor: isDesktop(width) ? Colors.transparent : null, ), - body: ListView( - children: [ - SectionLabel( - label: AppLocalizations.of(context)!.theme, - padding: const EdgeInsets.only(top: 10, left: 16, right: 16, bottom: 5), - ), - Column( - children: [ - CustomSwitchListTile( - value: selectedTheme == 0 ? true : false, - onChanged: (value) { - selectedTheme = value == true ? 0 : 1; - appConfigProvider.setSelectedTheme(value == true ? 0 : 1); - }, - title: AppLocalizations.of(context)!.systemDefined, - ), - const SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ThemeModeButton( - icon: Icons.light_mode, - value: 1, - selected: selectedTheme, - label: AppLocalizations.of(context)!.light, - onChanged: (value) { - selectedTheme = value; - appConfigProvider.setSelectedTheme(value); - }, - disabled: selectedTheme == 0 ? true : false, - ), - ThemeModeButton( - icon: Icons.dark_mode, - value: 2, - selected: selectedTheme, - label: AppLocalizations.of(context)!.dark, - onChanged: (value) { - selectedTheme = value; - appConfigProvider.setSelectedTheme(value); - }, - disabled: selectedTheme == 0 ? true : false, - ), - ], - ), - ], - ), - SectionLabel( - label: AppLocalizations.of(context)!.color, - padding: const EdgeInsets.only(top: 45, left: 16, right: 16, bottom: 5), - ), - if (appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31) CustomSwitchListTile( - value: dynamicColor, - onChanged: (value) { - setState(() => dynamicColor = value); - appConfigProvider.setUseDynamicColor(value); - }, - title: AppLocalizations.of(context)!.useDynamicTheme, - ), - if (!(appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31)) const SizedBox(height: 20), - if (dynamicColor == false) ...[ - SizedBox( - width: MediaQuery.of(context).size.width, - height: 70, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: colors.length, - itemBuilder: (context, index) { - if (index == 0) { - return Row( - children: [ - const SizedBox(width: 15), - ColorItem( - color: colors[index], - numericValue: index, - selectedValue: selectedColor, - onChanged: (value) { - setState(() => selectedColor = value); - appConfigProvider.setStaticColor(value); - } - ), - Container( - margin: const EdgeInsets.symmetric(horizontal: 10), - width: 1, - height: 60, - decoration: BoxDecoration( - color: Colors.grey, - borderRadius: BorderRadius.circular(1) - ), - ) - ], - ); - } - else if (index == colors.length-1) { - return Row( - children: [ - ColorItem( - color: colors[index], - numericValue: index, - selectedValue: selectedColor, - onChanged: (value) { - setState(() => selectedColor = value); - appConfigProvider.setStaticColor(value); - } - ), - const SizedBox(width: 15) - ], - ); - } - else { - return ColorItem( - color: colors[index], - numericValue: index, - selectedValue: selectedColor, - onChanged: (value) { - setState(() => selectedColor = value); - appConfigProvider.setStaticColor(value); - } - ); - } - }, - ), + body: SafeArea( + child: ListView( + children: [ + SectionLabel( + label: AppLocalizations.of(context)!.theme, + padding: const EdgeInsets.only(top: 10, left: 16, right: 16, bottom: 5), ), - Padding( - padding: const EdgeInsets.only( - left: 25, - top: 10 - ), - child: Text( - colorTranslation(context, selectedColor), - style: TextStyle( - color: Theme.of(context).listTileTheme.iconColor, - fontSize: 16 + Column( + children: [ + CustomSwitchListTile( + value: selectedTheme == 0 ? true : false, + onChanged: (value) { + selectedTheme = value == true ? 0 : 1; + appConfigProvider.setSelectedTheme(value == true ? 0 : 1); + }, + title: AppLocalizations.of(context)!.systemDefined, + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ThemeModeButton( + icon: Icons.light_mode, + value: 1, + selected: selectedTheme, + label: AppLocalizations.of(context)!.light, + onChanged: (value) { + selectedTheme = value; + appConfigProvider.setSelectedTheme(value); + }, + disabled: selectedTheme == 0 ? true : false, + ), + ThemeModeButton( + icon: Icons.dark_mode, + value: 2, + selected: selectedTheme, + label: AppLocalizations.of(context)!.dark, + onChanged: (value) { + selectedTheme = value; + appConfigProvider.setSelectedTheme(value); + }, + disabled: selectedTheme == 0 ? true : false, + ), + ], + ), + ], + ), + SectionLabel( + label: AppLocalizations.of(context)!.color, + padding: const EdgeInsets.only(top: 45, left: 16, right: 16, bottom: 5), + ), + if (appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31) CustomSwitchListTile( + value: dynamicColor, + onChanged: (value) { + setState(() => dynamicColor = value); + appConfigProvider.setUseDynamicColor(value); + }, + title: AppLocalizations.of(context)!.useDynamicTheme, + ), + if (!(appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31)) const SizedBox(height: 20), + if (dynamicColor == false) ...[ + SizedBox( + width: MediaQuery.of(context).size.width, + height: 70, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: colors.length, + itemBuilder: (context, index) { + if (index == 0) { + return Row( + children: [ + const SizedBox(width: 15), + ColorItem( + color: colors[index], + numericValue: index, + selectedValue: selectedColor, + onChanged: (value) { + setState(() => selectedColor = value); + appConfigProvider.setStaticColor(value); + } + ), + Container( + margin: const EdgeInsets.symmetric(horizontal: 10), + width: 1, + height: 60, + decoration: BoxDecoration( + color: Colors.grey, + borderRadius: BorderRadius.circular(1) + ), + ) + ], + ); + } + else if (index == colors.length-1) { + return Row( + children: [ + ColorItem( + color: colors[index], + numericValue: index, + selectedValue: selectedColor, + onChanged: (value) { + setState(() => selectedColor = value); + appConfigProvider.setStaticColor(value); + } + ), + const SizedBox(width: 15) + ], + ); + } + else { + return ColorItem( + color: colors[index], + numericValue: index, + selectedValue: selectedColor, + onChanged: (value) { + setState(() => selectedColor = value); + appConfigProvider.setStaticColor(value); + } + ); + } + }, ), ), + Padding( + padding: const EdgeInsets.only( + left: 25, + top: 10 + ), + child: Text( + colorTranslation(context, selectedColor), + style: TextStyle( + color: Theme.of(context).listTileTheme.iconColor, + fontSize: 16 + ), + ), + ) + ], + CustomSwitchListTile( + value: useThemeColorInsteadGreenRed, + onChanged: (value) { + setState(() => useThemeColorInsteadGreenRed = value); + appConfigProvider.setUseThemeColorForStatus(value); + }, + title: AppLocalizations.of(context)!.useThemeColorStatus, + subtitle: AppLocalizations.of(context)!.useThemeColorStatusDescription, ) ], - CustomSwitchListTile( - value: useThemeColorInsteadGreenRed, - onChanged: (value) { - setState(() => useThemeColorInsteadGreenRed = value); - appConfigProvider.setUseThemeColorForStatus(value); - }, - title: AppLocalizations.of(context)!.useThemeColorStatus, - subtitle: AppLocalizations.of(context)!.useThemeColorStatusDescription, - ) - ], + ), ), ); } diff --git a/lib/screens/settings/dhcp/add_static_lease_modal.dart b/lib/screens/settings/dhcp/add_static_lease_modal.dart index fd222d3..c80e848 100644 --- a/lib/screens/settings/dhcp/add_static_lease_modal.dart +++ b/lib/screens/settings/dhcp/add_static_lease_modal.dart @@ -3,7 +3,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/models/dhcp.dart'; -class AddStaticLeaseModal extends StatefulWidget { +class AddStaticLeaseModal extends StatelessWidget { final void Function(Lease) onConfirm; final bool dialog; @@ -14,10 +14,49 @@ class AddStaticLeaseModal extends StatefulWidget { }); @override - State createState() => _AddStaticLeaseModalState(); + Widget build(BuildContext context) { + if (dialog == true) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400 + ), + child: _Content(onConfirm: onConfirm) + ), + ); + } + else { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).dialogBackgroundColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) + ) + ), + child: SafeArea( + child: _Content(onConfirm: onConfirm) + ) + ), + ); + } + } } -class _AddStaticLeaseModalState extends State { +class _Content extends StatefulWidget { + final void Function(Lease) onConfirm; + + const _Content({ + required this.onConfirm + }); + + @override + State<_Content> createState() => __ContentState(); +} + +class __ContentState extends State<_Content> { final TextEditingController macController = TextEditingController(); String? macError; final TextEditingController ipController = TextEditingController(); @@ -67,175 +106,147 @@ class _AddStaticLeaseModalState extends State { @override Widget build(BuildContext context) { - Widget content() { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: SingleChildScrollView( - child: Wrap( - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 24), - child: Icon( - Icons.add, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.addStaticLease, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.only( - left: 24, right: 24, bottom: 12 - ), - child: TextFormField( - controller: macController, - onChanged: validateMac, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.smartphone_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: macError, - labelText: AppLocalizations.of(context)!.macAddress, - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - child: TextFormField( - controller: ipController, - onChanged: validateIp, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipError, - labelText: AppLocalizations.of(context)!.ipAddress, - ), - ), - ), - Padding( - padding: const EdgeInsets.only( - left: 24, right: 24, top: 12 - ), - child: TextFormField( - controller: hostNameController, - onChanged: (value) { - if (value != '') { - setState(() => hostNameError = null); - } - else { - setState(() => hostNameError = AppLocalizations.of(context)!.hostNameError); - } - validateData(); - }, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.badge_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: hostNameError, - labelText: AppLocalizations.of(context)!.hostName, - ), - ), - ), - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.all(24), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SingleChildScrollView( + child: Wrap( children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel), + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 24), + child: Icon( + Icons.add, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.addStaticLease, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ), + ], + ), ), - const SizedBox(width: 20), - TextButton( - onPressed: validData == true - ? () { - Navigator.pop(context); - widget.onConfirm( - Lease( - mac: macController.text, - hostname: hostNameController.text, - ip: ipController.text + Padding( + padding: const EdgeInsets.only( + left: 24, right: 24, bottom: 12 + ), + child: TextFormField( + controller: macController, + onChanged: validateMac, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.smartphone_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) ) - ); - } - : null, - child: Text( - AppLocalizations.of(context)!.confirm, - style: TextStyle( - color: validData == true - ? Theme.of(context).colorScheme.primary - : Colors.grey + ), + errorText: macError, + labelText: AppLocalizations.of(context)!.macAddress, + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: TextFormField( + controller: ipController, + onChanged: validateIp, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.link_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipError, + labelText: AppLocalizations.of(context)!.ipAddress, + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 24, right: 24, top: 12 + ), + child: TextFormField( + controller: hostNameController, + onChanged: (value) { + if (value != '') { + setState(() => hostNameError = null); + } + else { + setState(() => hostNameError = AppLocalizations.of(context)!.hostNameError); + } + validateData(); + }, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.badge_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: hostNameError, + labelText: AppLocalizations.of(context)!.hostName, ), ), ), ], ), - ) - ], - ); - } - - 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( - color: Theme.of(context).dialogBackgroundColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28) - ) + Padding( + padding: const EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel), + ), + const SizedBox(width: 20), + TextButton( + onPressed: validData == true + ? () { + Navigator.pop(context); + widget.onConfirm( + Lease( + mac: macController.text, + hostname: hostNameController.text, + ip: ipController.text + ) + ); + } + : null, + child: Text( + AppLocalizations.of(context)!.confirm, + style: TextStyle( + color: validData == true + ? Theme.of(context).colorScheme.primary + : Colors.grey + ), + ), + ), + ], ), - child: content() - ), - ); - } + ) + ], + ); } } \ No newline at end of file diff --git a/lib/screens/settings/dhcp/dhcp.dart b/lib/screens/settings/dhcp/dhcp.dart index fff464e..75d1856 100644 --- a/lib/screens/settings/dhcp/dhcp.dart +++ b/lib/screens/settings/dhcp/dhcp.dart @@ -411,328 +411,330 @@ class _DhcpScreenState extends State { const SizedBox(width: 10) ] : null, ), - body: Builder( - builder: (context) { - switch (dhcpProvider.loadStatus) { - case LoadStatus.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)!.loadingDhcp, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - if (selectedInterface != null) { - return SingleChildScrollView( - child: Wrap( + body: SafeArea( + child: Builder( + builder: (context) { + switch (dhcpProvider.loadStatus) { + case LoadStatus.loading: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - DhcpMainButton( - selectedInterface: selectedInterface, - enabled: enabled, - setEnabled: (v) => setState(() => enabled = v) - ), - if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[ - SectionLabel( - label: AppLocalizations.of(context)!.ipv4settings, - padding: const EdgeInsets.only( - top: 24, left: 16, right: 16, bottom: 8 - ) + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingDhcp, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), - _DhcpField( - icon: Icons.skip_previous_rounded, - label: AppLocalizations.of(context)!.startOfRange, - controller: ipv4StartRangeController, - onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid), - error: ipv4StartRangeError - ), - _DhcpField( - icon: Icons.skip_next_rounded, - label: AppLocalizations.of(context)!.endOfRange, - controller: ipv4EndRangeController, - onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid), - error: ipv4EndRangeError - ), - _DhcpField( - icon: Icons.hub_rounded, - label: AppLocalizations.of(context)!.subnetMask, - controller: ipv4SubnetMaskController, - onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid), - error: ipv4SubnetMaskError - ), - _DhcpField( - icon: Icons.router_rounded, - label: AppLocalizations.of(context)!.gateway, - controller: ipv4GatewayController, - onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid), - error: ipv4GatewayError - ), - _DhcpField( - icon: Icons.timer, - label: AppLocalizations.of(context)!.leaseTime, - controller: ipv4LeaseTimeController, - onChanged: (value) { - if (int.tryParse(value).runtimeType == int) { - setState(() => ipv4LeaseTimeError = null); - } - else { - setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); - } - }, - error: ipv4LeaseTimeError - ), - ], - if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[ - SectionLabel( - label: AppLocalizations.of(context)!.ipv6settings, - padding: const EdgeInsets.all(16) - ), - _DhcpField( - icon: Icons.skip_next_rounded, - label: AppLocalizations.of(context)!.startOfRange, - controller: ipv6StartRangeController, - onChanged: (value) => validateIpV6(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid), - error: ipv6StartRangeError - ), - _DhcpField( - icon: Icons.skip_previous_rounded, - label: AppLocalizations.of(context)!.endOfRange, - controller: ipv6EndRangeController, - onChanged: (value) => validateIpV6(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid), - error: ipv6EndRangeError - ), - _DhcpField( - icon: Icons.timer, - label: AppLocalizations.of(context)!.leaseTime, - controller: ipv6LeaseTimeController, - onChanged: (value) { - if (int.tryParse(value).runtimeType == int) { - setState(() => ipv6LeaseTimeError = null); - } - else { - setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); - } - }, - error: ipv6LeaseTimeError - ) - ], - const SizedBox(height: 20), - SectionLabel( - label: AppLocalizations.of(context)!.dhcpLeases, - padding: const EdgeInsets.all(16), - ), - if (width <= 900) Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus!.leases, - staticLeases: false, - ) - ) - ); - }, - child: Container( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.dhcpLeases, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - Icon( - Icons.arrow_forward_rounded, - color: Theme.of(context).colorScheme.onSurface, - ) - ], - ), - ), - ), - ), - if (width <= 900) Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases, - staticLeases: true, - ) - ) - ); - }, - child: Container( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.dhcpStatic, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - Icon( - Icons.arrow_forward_rounded, - color: Theme.of(context).colorScheme.onSurface, - ) - ], - ), - ), - ), - ), - if (width > 900) Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ElevatedButton( - onPressed: () { - if (!(Platform.isAndroid || Platform.isIOS)) { - SplitView.of(context).push( - DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus!.leases, - staticLeases: false, - ) - ); - } - else { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus!.leases, - staticLeases: false, - ) - ) - ); - } - }, - child: Row( - children: [ - Text(AppLocalizations.of(context)!.dhcpLeases), - const SizedBox(width: 8), - const Icon(Icons.arrow_forward_rounded) - ], - ) - ), - ElevatedButton( - onPressed: () { - if (!(Platform.isAndroid || Platform.isIOS)) { - SplitView.of(context).push( - DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases, - staticLeases: true, - ) - ); - } - else { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases, - staticLeases: true, - ) - ) - ); - } - }, - child: Row( - children: [ - Text(AppLocalizations.of(context)!.dhcpStatic), - const SizedBox(width: 8), - const Icon(Icons.arrow_forward_rounded) - ], - ) - ), - ], - ), - const SizedBox(height: 10) + ) ], ), ); - } - else { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - AppLocalizations.of(context)!.neededSelectInterface, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5) + + case LoadStatus.loaded: + if (selectedInterface != null) { + return SingleChildScrollView( + child: Wrap( + children: [ + DhcpMainButton( + selectedInterface: selectedInterface, + enabled: enabled, + setEnabled: (v) => setState(() => enabled = v) + ), + if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[ + SectionLabel( + label: AppLocalizations.of(context)!.ipv4settings, + padding: const EdgeInsets.only( + top: 24, left: 16, right: 16, bottom: 8 + ) + ), + _DhcpField( + icon: Icons.skip_previous_rounded, + label: AppLocalizations.of(context)!.startOfRange, + controller: ipv4StartRangeController, + onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid), + error: ipv4StartRangeError + ), + _DhcpField( + icon: Icons.skip_next_rounded, + label: AppLocalizations.of(context)!.endOfRange, + controller: ipv4EndRangeController, + onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid), + error: ipv4EndRangeError + ), + _DhcpField( + icon: Icons.hub_rounded, + label: AppLocalizations.of(context)!.subnetMask, + controller: ipv4SubnetMaskController, + onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid), + error: ipv4SubnetMaskError + ), + _DhcpField( + icon: Icons.router_rounded, + label: AppLocalizations.of(context)!.gateway, + controller: ipv4GatewayController, + onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid), + error: ipv4GatewayError + ), + _DhcpField( + icon: Icons.timer, + label: AppLocalizations.of(context)!.leaseTime, + controller: ipv4LeaseTimeController, + onChanged: (value) { + if (int.tryParse(value).runtimeType == int) { + setState(() => ipv4LeaseTimeError = null); + } + else { + setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); + } + }, + error: ipv4LeaseTimeError + ), + ], + if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[ + SectionLabel( + label: AppLocalizations.of(context)!.ipv6settings, + padding: const EdgeInsets.all(16) + ), + _DhcpField( + icon: Icons.skip_next_rounded, + label: AppLocalizations.of(context)!.startOfRange, + controller: ipv6StartRangeController, + onChanged: (value) => validateIpV6(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid), + error: ipv6StartRangeError + ), + _DhcpField( + icon: Icons.skip_previous_rounded, + label: AppLocalizations.of(context)!.endOfRange, + controller: ipv6EndRangeController, + onChanged: (value) => validateIpV6(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid), + error: ipv6EndRangeError + ), + _DhcpField( + icon: Icons.timer, + label: AppLocalizations.of(context)!.leaseTime, + controller: ipv6LeaseTimeController, + onChanged: (value) { + if (int.tryParse(value).runtimeType == int) { + setState(() => ipv6LeaseTimeError = null); + } + else { + setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); + } + }, + error: ipv6LeaseTimeError + ) + ], + const SizedBox(height: 20), + SectionLabel( + label: AppLocalizations.of(context)!.dhcpLeases, + padding: const EdgeInsets.all(16), + ), + if (width <= 900) Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus!.leases, + staticLeases: false, + ) + ) + ); + }, + child: Container( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.dhcpLeases, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + Icon( + Icons.arrow_forward_rounded, + color: Theme.of(context).colorScheme.onSurface, + ) + ], ), ), ), - const SizedBox(height: 30), - ElevatedButton( - onPressed: selectInterface, - child: Text(AppLocalizations.of(context)!.selectInterface) + ), + if (width <= 900) Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases, + staticLeases: true, + ) + ) + ); + }, + child: Container( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.dhcpStatic, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + Icon( + Icons.arrow_forward_rounded, + color: Theme.of(context).colorScheme.onSurface, + ) + ], + ), + ), ), - ], - ), + ), + if (width > 900) Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: () { + if (!(Platform.isAndroid || Platform.isIOS)) { + SplitView.of(context).push( + DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus!.leases, + staticLeases: false, + ) + ); + } + else { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus!.leases, + staticLeases: false, + ) + ) + ); + } + }, + child: Row( + children: [ + Text(AppLocalizations.of(context)!.dhcpLeases), + const SizedBox(width: 8), + const Icon(Icons.arrow_forward_rounded) + ], + ) + ), + ElevatedButton( + onPressed: () { + if (!(Platform.isAndroid || Platform.isIOS)) { + SplitView.of(context).push( + DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases, + staticLeases: true, + ) + ); + } + else { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases, + staticLeases: true, + ) + ) + ); + } + }, + child: Row( + children: [ + Text(AppLocalizations.of(context)!.dhcpStatic), + const SizedBox(width: 8), + const Icon(Icons.arrow_forward_rounded) + ], + ) + ), + ], + ), + const SizedBox(height: 10) + ], ), - ], + ); + } + else { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + AppLocalizations.of(context)!.neededSelectInterface, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5) + ), + ), + ), + const SizedBox(height: 30), + ElevatedButton( + onPressed: selectInterface, + child: Text(AppLocalizations.of(context)!.selectInterface) + ), + ], + ), + ), + ], + ); + } + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.dhcpSettingsNotLoaded, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), ); - } - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.dhcpSettingsNotLoaded, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - }, + + default: + return const SizedBox(); + } + }, + ), ) ); } diff --git a/lib/screens/settings/dhcp/dhcp_leases.dart b/lib/screens/settings/dhcp/dhcp_leases.dart index 91bd1cf..9bb1fe3 100644 --- a/lib/screens/settings/dhcp/dhcp_leases.dart +++ b/lib/screens/settings/dhcp/dhcp_leases.dart @@ -22,10 +22,10 @@ class DhcpLeases extends StatelessWidget { final bool staticLeases; const DhcpLeases({ - Key? key, + super.key, required this.items, required this.staticLeases, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -130,32 +130,34 @@ class DhcpLeases extends StatelessWidget { ), ), body: items.isNotEmpty - ? ListView.builder( - padding: const EdgeInsets.only(top: 0), - itemCount: items.length, - itemBuilder: (context, index) => ListTile( - isThreeLine: true, - title: Text(items[index].ip), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(items[index].mac), - Text(items[index].hostname), - ], + ? SafeArea( + child: ListView.builder( + padding: const EdgeInsets.only(top: 0), + itemCount: items.length, + itemBuilder: (context, index) => ListTile( + isThreeLine: true, + title: Text(items[index].ip), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(items[index].mac), + Text(items[index].hostname), + ], + ), + trailing: staticLeases == true + ? IconButton( + onPressed: () { + showModal( + context: context, + builder: (context) => DeleteStaticLeaseModal( + onConfirm: () => deleteLease(items[index]) + ) + ); + }, + icon: const Icon(Icons.delete) + ) + : null, ), - trailing: staticLeases == true - ? IconButton( - onPressed: () { - showModal( - context: context, - builder: (context) => DeleteStaticLeaseModal( - onConfirm: () => deleteLease(items[index]) - ) - ); - }, - icon: const Icon(Icons.delete) - ) - : null, ), ) : Center( diff --git a/lib/screens/settings/dhcp/select_interface_modal.dart b/lib/screens/settings/dhcp/select_interface_modal.dart index e2dc26d..a9c9c67 100644 --- a/lib/screens/settings/dhcp/select_interface_modal.dart +++ b/lib/screens/settings/dhcp/select_interface_modal.dart @@ -137,16 +137,17 @@ class SelectInterfaceModal extends StatelessWidget { ), ), Expanded( - child: ListView.builder( - controller: controller, - itemCount: interfaces.length, - itemBuilder: (context, index) => DhcpInterfaceItem( - networkInterface: interfaces[index], - onSelect: onSelect - ) + child: SafeArea( + child: ListView.builder( + controller: controller, + itemCount: interfaces.length, + itemBuilder: (context, index) => DhcpInterfaceItem( + networkInterface: interfaces[index], + onSelect: onSelect + ) + ), ) ), - const SizedBox(height: 16) ], ), ); diff --git a/lib/screens/settings/dns/bootstrap_dns.dart b/lib/screens/settings/dns/bootstrap_dns.dart index 2ae3f4f..c970f34 100644 --- a/lib/screens/settings/dns/bootstrap_dns.dart +++ b/lib/screens/settings/dns/bootstrap_dns.dart @@ -117,105 +117,107 @@ class _BootstrapDnsScreenState extends State { const SizedBox(width: 10) ], ), - body: ListView( - padding: const EdgeInsets.only(top: 10), - children: [ - Card( - margin: const EdgeInsets.only( - left: 16, right: 16, bottom: 20 - ), - child: Padding( - padding: const EdgeInsets.all(20), - child: Row( - children: [ - Icon( - Icons.info_rounded, - color: Theme.of(context).listTileTheme.iconColor, - ), - const SizedBox(width: 20), - Flexible( - child: Text( - AppLocalizations.of(context)!.bootstrapDnsServersInfo, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), + body: SafeArea( + child: ListView( + padding: const EdgeInsets.only(top: 10), + children: [ + Card( + margin: const EdgeInsets.only( + left: 16, right: 16, bottom: 20 + ), + child: Padding( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + Icon( + Icons.info_rounded, + color: Theme.of(context).listTileTheme.iconColor, + ), + const SizedBox(width: 20), + Flexible( + child: Text( + AppLocalizations.of(context)!.bootstrapDnsServersInfo, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) ) - ) - ], + ], + ), ), ), - ), - const SizedBox(height: 10), - if (bootstrapControllers.isEmpty) Column( - children: [ - Padding( - padding: const EdgeInsets.all(10), - child: Center( - child: Text( - AppLocalizations.of(context)!.noBootstrapDns, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontSize: 16 + const SizedBox(height: 10), + if (bootstrapControllers.isEmpty) Column( + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: Center( + child: Text( + AppLocalizations.of(context)!.noBootstrapDns, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 16 + ), ), ), ), - ), - const SizedBox(height: 20), - ], - ), - ...bootstrapControllers.map((c) => Padding( - padding: const EdgeInsets.only( - left: 16, right: 6, bottom: 20 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: TextFormField( - controller: c['controller'], - onChanged: (value) => validateIp(c, value), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.dns_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: c['error'], - labelText: AppLocalizations.of(context)!.dnsServer, - ) - ), - ), - const SizedBox(width: 8), - IconButton( - onPressed: () { - setState(() => bootstrapControllers = bootstrapControllers.where((con) => con != c).toList()); - checkValidValues(); - }, - icon: const Icon(Icons.remove_circle_outline) - ) + const SizedBox(height: 20), ], ), - )).toList(), - Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - ElevatedButton.icon( - onPressed: () { - setState(() => bootstrapControllers.add({ - 'controller': TextEditingController(), - 'error': null - })); - checkValidValues(); - }, - icon: const Icon(Icons.add), - label: Text(AppLocalizations.of(context)!.addItem) + ...bootstrapControllers.map((c) => Padding( + padding: const EdgeInsets.only( + left: 16, right: 6, bottom: 20 ), - ], - ), - const SizedBox(height: 20) - ], + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: TextFormField( + controller: c['controller'], + onChanged: (value) => validateIp(c, value), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.dns_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: c['error'], + labelText: AppLocalizations.of(context)!.dnsServer, + ) + ), + ), + const SizedBox(width: 8), + IconButton( + onPressed: () { + setState(() => bootstrapControllers = bootstrapControllers.where((con) => con != c).toList()); + checkValidValues(); + }, + icon: const Icon(Icons.remove_circle_outline) + ) + ], + ), + )).toList(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton.icon( + onPressed: () { + setState(() => bootstrapControllers.add({ + 'controller': TextEditingController(), + 'error': null + })); + checkValidValues(); + }, + icon: const Icon(Icons.add), + label: Text(AppLocalizations.of(context)!.addItem) + ), + ], + ), + const SizedBox(height: 20) + ], + ), ), ); } diff --git a/lib/screens/settings/dns/cache_config.dart b/lib/screens/settings/dns/cache_config.dart index 7f6f63a..40a99f8 100644 --- a/lib/screens/settings/dns/cache_config.dart +++ b/lib/screens/settings/dns/cache_config.dart @@ -169,81 +169,83 @@ class _CacheConfigDnsScreenState extends State { const SizedBox(width: 10) ], ), - body: ListView( - padding: const EdgeInsets.only(top: 10), - children: [ - numericField( - controller: cacheSizeController, - label: AppLocalizations.of(context)!.cacheSize, - helper: AppLocalizations.of(context)!.inBytes, - error: cacheSizeError, - onChanged: (value) { - if (int.tryParse(value) != null) { - setState(() => cacheSizeError = null); + body: SafeArea( + child: ListView( + padding: const EdgeInsets.only(top: 10), + children: [ + numericField( + controller: cacheSizeController, + label: AppLocalizations.of(context)!.cacheSize, + helper: AppLocalizations.of(context)!.inBytes, + error: cacheSizeError, + onChanged: (value) { + if (int.tryParse(value) != null) { + setState(() => cacheSizeError = null); + } + else { + setState(() => cacheSizeError = AppLocalizations.of(context)!.valueNotNumber); + } + checkValidData(); } - else { - setState(() => cacheSizeError = AppLocalizations.of(context)!.valueNotNumber); + ), + const SizedBox(height: 30), + numericField( + controller: overrideMinTtlController, + label: AppLocalizations.of(context)!.overrideMinimumTtl, + helper: AppLocalizations.of(context)!.overrideMinimumTtlDescription, + error: overrideMinTtlError, + onChanged: (value) { + if (int.tryParse(value) != null) { + setState(() => overrideMinTtlError = null); + } + else { + setState(() => overrideMinTtlError = AppLocalizations.of(context)!.valueNotNumber); + } + checkValidData(); } - checkValidData(); - } - ), - const SizedBox(height: 30), - numericField( - controller: overrideMinTtlController, - label: AppLocalizations.of(context)!.overrideMinimumTtl, - helper: AppLocalizations.of(context)!.overrideMinimumTtlDescription, - error: overrideMinTtlError, - onChanged: (value) { - if (int.tryParse(value) != null) { - setState(() => overrideMinTtlError = null); + ), + const SizedBox(height: 30), + numericField( + controller: overrideMaxTtlController, + label: AppLocalizations.of(context)!.overrideMaximumTtl, + helper: AppLocalizations.of(context)!.overrideMaximumTtlDescription, + error: overrideMaxTtlError, + onChanged: (value) { + if (int.tryParse(value) != null) { + setState(() => overrideMaxTtlError = null); + } + else { + setState(() => overrideMaxTtlError = AppLocalizations.of(context)!.valueNotNumber); + } + checkValidData(); } - else { - setState(() => overrideMinTtlError = AppLocalizations.of(context)!.valueNotNumber); - } - checkValidData(); - } - ), - const SizedBox(height: 30), - numericField( - controller: overrideMaxTtlController, - label: AppLocalizations.of(context)!.overrideMaximumTtl, - helper: AppLocalizations.of(context)!.overrideMaximumTtlDescription, - error: overrideMaxTtlError, - onChanged: (value) { - if (int.tryParse(value) != null) { - setState(() => overrideMaxTtlError = null); - } - else { - setState(() => overrideMaxTtlError = AppLocalizations.of(context)!.valueNotNumber); - } - checkValidData(); - } - ), - const SizedBox(height: 10), - CustomSwitchListTile( - value: optimisticCache, - onChanged: (value) => setState(() => optimisticCache = value), - title: AppLocalizations.of(context)!.optimisticCaching, - subtitle: AppLocalizations.of(context)!.optimisticCachingDescription, - ), - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton.icon( - onPressed: () => showDialog( - context: context, - builder: (context) => ClearDnsCacheDialog( - onConfirm: clearCache - ) - ), - icon: const Icon(Icons.delete_rounded), - label: Text(AppLocalizations.of(context)!.clearDnsCache), - ), - ], - ), - const SizedBox(height: 16) - ], + ), + const SizedBox(height: 10), + CustomSwitchListTile( + value: optimisticCache, + onChanged: (value) => setState(() => optimisticCache = value), + title: AppLocalizations.of(context)!.optimisticCaching, + subtitle: AppLocalizations.of(context)!.optimisticCachingDescription, + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton.icon( + onPressed: () => showDialog( + context: context, + builder: (context) => ClearDnsCacheDialog( + onConfirm: clearCache + ) + ), + icon: const Icon(Icons.delete_rounded), + label: Text(AppLocalizations.of(context)!.clearDnsCache), + ), + ], + ), + const SizedBox(height: 16) + ], + ), ), ); } diff --git a/lib/screens/settings/dns/comment_modal.dart b/lib/screens/settings/dns/comment_modal.dart index a9dd60d..92d4c0f 100644 --- a/lib/screens/settings/dns/comment_modal.dart +++ b/lib/screens/settings/dns/comment_modal.dart @@ -1,141 +1,30 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -class CommentModal extends StatefulWidget { +class CommentModal extends StatelessWidget { final String? comment; final void Function(String) onConfirm; final bool dialog; const CommentModal({ - Key? key, + super.key, this.comment, required this.onConfirm, required this.dialog - }) : super(key: key); - - @override - State createState() => _CommentModalState(); -} - -class _CommentModalState extends State { - final TextEditingController commentController = TextEditingController(); - - bool validData = false; - - @override - void initState() { - if (widget.comment != null) { - commentController.text = widget.comment!.replaceFirst(RegExp(r'#(\s)?'), ""); - } - super.initState(); - } + }); @override Widget build(BuildContext context) { - 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.comment_rounded, - size: 24, - color: Theme.of(context).colorScheme.secondary, - ), - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.comment, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface - ), - ), - const SizedBox(height: 16), - ], - ), - ], - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: TextFormField( - controller: commentController, - onChanged: (value) { - if (value != '') { - setState(() => validData = true); - } - else { - setState(() => validData = false); - } - }, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.comment_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.comment, - helperText: AppLocalizations.of(context)!.commentsDescription, - helperMaxLines: 3 - ) - ), - ), - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.all(24), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) - ), - const SizedBox(width: 20), - TextButton( - onPressed: validData == true - ? () { - Navigator.pop(context); - widget.onConfirm("# ${commentController.text}"); - } - : null, - child: Text( - AppLocalizations.of(context)!.confirm, - style: TextStyle( - color: validData == true - ? Theme.of(context).colorScheme.primary - : Colors.grey - ), - ) - ), - ], - ), - ) - ], - ); - } - - if (widget.dialog == true) { + if (dialog == true) { return Dialog( child: ConstrainedBox( constraints: const BoxConstraints( maxWidth: 400 ), - child: content() + child: _Content( + comment: comment, + onConfirm: onConfirm, + ) ), ); } @@ -150,9 +39,139 @@ class _CommentModalState extends State { ), color: Theme.of(context).dialogBackgroundColor ), - child: content() + child: SafeArea( + child: _Content( + comment: comment, + onConfirm: onConfirm, + ), + ) ), ); } } +} + +class _Content extends StatefulWidget { + final String? comment; + final void Function(String) onConfirm; + + const _Content({ + required this.comment, + required this.onConfirm + }); + + @override + State<_Content> createState() => __ContentState(); +} + +class __ContentState extends State<_Content> { + final TextEditingController commentController = TextEditingController(); + + bool validData = false; + + @override + void initState() { + if (widget.comment != null) { + commentController.text = widget.comment!.replaceFirst(RegExp(r'#(\s)?'), ""); + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SingleChildScrollView( + child: Wrap( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 24), + child: Icon( + Icons.comment_rounded, + size: 24, + color: Theme.of(context).colorScheme.secondary, + ), + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.comment, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), + ), + const SizedBox(height: 16), + ], + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextFormField( + controller: commentController, + onChanged: (value) { + if (value != '') { + setState(() => validData = true); + } + else { + setState(() => validData = false); + } + }, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.comment_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.comment, + helperText: AppLocalizations.of(context)!.commentsDescription, + helperMaxLines: 3 + ) + ), + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ), + const SizedBox(width: 20), + TextButton( + onPressed: validData == true + ? () { + Navigator.pop(context); + widget.onConfirm("# ${commentController.text}"); + } + : null, + child: Text( + AppLocalizations.of(context)!.confirm, + style: TextStyle( + color: validData == true + ? Theme.of(context).colorScheme.primary + : Colors.grey + ), + ) + ), + ], + ), + ) + ], + ); + } } \ No newline at end of file diff --git a/lib/screens/settings/dns/dns.dart b/lib/screens/settings/dns/dns.dart index 02ec5d6..2c711d6 100644 --- a/lib/screens/settings/dns/dns.dart +++ b/lib/screens/settings/dns/dns.dart @@ -25,9 +25,9 @@ class DnsSettings extends StatefulWidget { final bool splitView; const DnsSettings({ - Key? key, + super.key, required this.splitView, - }) : super(key: key); + }); @override State createState() => _DnsSettingsState(); @@ -118,95 +118,97 @@ class _DnsSettingsState extends State { const SizedBox(width: 10) ], ), - body: Builder( - builder: (context) { - switch (dnsProvider.loadStatus) { - case LoadStatus.loading: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + body: SafeArea( + child: Builder( + builder: (context) { + switch (dnsProvider.loadStatus) { + case LoadStatus.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)!.loadingDnsConfig, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ) + ); + + case LoadStatus.loaded: + return ListView( children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingDnsConfig, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ) - ); - - case LoadStatus.loaded: - return ListView( - children: [ - CustomListTile( - title: AppLocalizations.of(context)!.upstreamDns, - subtitle: AppLocalizations.of(context)!.upstreamDnsDescription, - onTap: () => navigate(const UpstreamDnsScreen()), - icon: Icons.upload_rounded, - ), - CustomListTile( - title: AppLocalizations.of(context)!.bootstrapDns, - subtitle: AppLocalizations.of(context)!.bootstrapDnsDescription, - onTap: () => navigate(const BootstrapDnsScreen()), - icon: Icons.dns_rounded, - ), - CustomListTile( - title: AppLocalizations.of(context)!.privateReverseDnsServers, - subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription, - onTap: () => navigate(const PrivateReverseDnsServersScreen()), - icon: Icons.person_rounded, - ), - CustomListTile( - title: AppLocalizations.of(context)!.dnsServerSettings, - subtitle: AppLocalizations.of(context)!.dnsServerSettingsDescription, - onTap: () => navigate(const DnsServerSettingsScreen()), - icon: Icons.settings, - ), - CustomListTile( - title: AppLocalizations.of(context)!.dnsCacheConfig, - subtitle: AppLocalizations.of(context)!.dnsCacheConfigDescription, - onTap: () => navigate(const CacheConfigDnsScreen()), - icon: Icons.storage_rounded, - ), - ], - ); - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, + CustomListTile( + title: AppLocalizations.of(context)!.upstreamDns, + subtitle: AppLocalizations.of(context)!.upstreamDnsDescription, + onTap: () => navigate(const UpstreamDnsScreen()), + icon: Icons.upload_rounded, + ), + CustomListTile( + title: AppLocalizations.of(context)!.bootstrapDns, + subtitle: AppLocalizations.of(context)!.bootstrapDnsDescription, + onTap: () => navigate(const BootstrapDnsScreen()), + icon: Icons.dns_rounded, + ), + CustomListTile( + title: AppLocalizations.of(context)!.privateReverseDnsServers, + subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription, + onTap: () => navigate(const PrivateReverseDnsServersScreen()), + icon: Icons.person_rounded, + ), + CustomListTile( + title: AppLocalizations.of(context)!.dnsServerSettings, + subtitle: AppLocalizations.of(context)!.dnsServerSettingsDescription, + onTap: () => navigate(const DnsServerSettingsScreen()), + icon: Icons.settings, + ), + CustomListTile( + title: AppLocalizations.of(context)!.dnsCacheConfig, + subtitle: AppLocalizations.of(context)!.dnsCacheConfigDescription, + onTap: () => navigate(const CacheConfigDnsScreen()), + icon: Icons.storage_rounded, ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.dnsConfigNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) ], - ), - ); - - default: - return const SizedBox(); - } - }, + ); + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.dnsConfigNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, + ), ) ); } diff --git a/lib/screens/settings/dns/dns_server_settings.dart b/lib/screens/settings/dns/dns_server_settings.dart index 777821f..4cc650a 100644 --- a/lib/screens/settings/dns/dns_server_settings.dart +++ b/lib/screens/settings/dns/dns_server_settings.dart @@ -167,141 +167,143 @@ class _DnsServerSettingsScreenState extends State { const SizedBox(width: 10) ], ), - body: ListView( - padding: const EdgeInsets.only(top: 10), - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: TextFormField( - controller: limitRequestsController, - onChanged: (value) { - if (int.tryParse(value) != null) { - setState(() => limitRequestsError = null); - } - else { - setState(() => limitRequestsError = AppLocalizations.of(context)!.valueNotNumber); - } - validateData(); - }, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.looks_one_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.limitRequestsSecond, - errorText: limitRequestsError - ), - keyboardType: TextInputType.number, - ), - ), - const SizedBox(height: 10), - CustomSwitchListTile( - value: enableEdns, - onChanged: (value) => setState(() => enableEdns = value), - title: AppLocalizations.of(context)!.enableEdns, - subtitle: AppLocalizations.of(context)!.enableEdnsDescription, - ), - CustomSwitchListTile( - value: enableDnssec, - onChanged: (value) => setState(() => enableDnssec = value), - title: AppLocalizations.of(context)!.enableDnssec, - subtitle: AppLocalizations.of(context)!.enableDnssecDescription, - ), - CustomSwitchListTile( - value: disableIpv6Resolving, - onChanged: (value) => setState(() => disableIpv6Resolving = value), - title: AppLocalizations.of(context)!.disableResolvingIpv6, - subtitle: AppLocalizations.of(context)!.disableResolvingIpv6Description, - ), - SectionLabel(label: AppLocalizations.of(context)!.blockingMode), - CustomRadioListTile( - groupValue: blockingMode, - value: "default", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: AppLocalizations.of(context)!.defaultMode, - subtitle: AppLocalizations.of(context)!.defaultDescription, - onChanged: updateBlockingMode, - ), - CustomRadioListTile( - groupValue: blockingMode, - value: "refused", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: "REFUSED", - subtitle: AppLocalizations.of(context)!.refusedDescription, - onChanged: updateBlockingMode, - ), - CustomRadioListTile( - groupValue: blockingMode, - value: "nxdomain", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: "NXDOMAIN", - subtitle: AppLocalizations.of(context)!.nxdomainDescription, - onChanged: updateBlockingMode, - ), - CustomRadioListTile( - groupValue: blockingMode, - value: "null_ip", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: AppLocalizations.of(context)!.nullIp, - subtitle: AppLocalizations.of(context)!.nullIpDescription, - onChanged: updateBlockingMode, - ), - CustomRadioListTile( - groupValue: blockingMode, - value: "custom_ip", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: AppLocalizations.of(context)!.customIp, - subtitle: AppLocalizations.of(context)!.customIpDescription, - onChanged: updateBlockingMode, - ), - const SizedBox(height: 10), - if (blockingMode == 'custom_ip') ...[ + body: SafeArea( + child: ListView( + padding: const EdgeInsets.only(top: 10), + children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), + padding: const EdgeInsets.symmetric(horizontal: 16), child: TextFormField( - controller: ipv4controller, - onChanged: validateIpv4, + controller: limitRequestsController, + onChanged: (value) { + if (int.tryParse(value) != null) { + setState(() => limitRequestsError = null); + } + else { + setState(() => limitRequestsError = AppLocalizations.of(context)!.valueNotNumber); + } + validateData(); + }, decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), + prefixIcon: const Icon(Icons.looks_one_rounded), border: const OutlineInputBorder( borderRadius: BorderRadius.all( Radius.circular(10) ) ), - errorText: ipv4error, - helperText: AppLocalizations.of(context)!.blockingIpv4Description, - helperMaxLines: 10, - labelText: AppLocalizations.of(context)!.blockingIpv4, + labelText: AppLocalizations.of(context)!.limitRequestsSecond, + errorText: limitRequestsError ), keyboardType: TextInputType.number, ), ), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: TextFormField( - controller: ipv6controller, - onChanged: validateIpv6, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) + const SizedBox(height: 10), + CustomSwitchListTile( + value: enableEdns, + onChanged: (value) => setState(() => enableEdns = value), + title: AppLocalizations.of(context)!.enableEdns, + subtitle: AppLocalizations.of(context)!.enableEdnsDescription, + ), + CustomSwitchListTile( + value: enableDnssec, + onChanged: (value) => setState(() => enableDnssec = value), + title: AppLocalizations.of(context)!.enableDnssec, + subtitle: AppLocalizations.of(context)!.enableDnssecDescription, + ), + CustomSwitchListTile( + value: disableIpv6Resolving, + onChanged: (value) => setState(() => disableIpv6Resolving = value), + title: AppLocalizations.of(context)!.disableResolvingIpv6, + subtitle: AppLocalizations.of(context)!.disableResolvingIpv6Description, + ), + SectionLabel(label: AppLocalizations.of(context)!.blockingMode), + CustomRadioListTile( + groupValue: blockingMode, + value: "default", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.defaultMode, + subtitle: AppLocalizations.of(context)!.defaultDescription, + onChanged: updateBlockingMode, + ), + CustomRadioListTile( + groupValue: blockingMode, + value: "refused", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: "REFUSED", + subtitle: AppLocalizations.of(context)!.refusedDescription, + onChanged: updateBlockingMode, + ), + CustomRadioListTile( + groupValue: blockingMode, + value: "nxdomain", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: "NXDOMAIN", + subtitle: AppLocalizations.of(context)!.nxdomainDescription, + onChanged: updateBlockingMode, + ), + CustomRadioListTile( + groupValue: blockingMode, + value: "null_ip", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.nullIp, + subtitle: AppLocalizations.of(context)!.nullIpDescription, + onChanged: updateBlockingMode, + ), + CustomRadioListTile( + groupValue: blockingMode, + value: "custom_ip", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.customIp, + subtitle: AppLocalizations.of(context)!.customIpDescription, + onChanged: updateBlockingMode, + ), + const SizedBox(height: 10), + if (blockingMode == 'custom_ip') ...[ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextFormField( + controller: ipv4controller, + onChanged: validateIpv4, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.link_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv4error, + helperText: AppLocalizations.of(context)!.blockingIpv4Description, + helperMaxLines: 10, + labelText: AppLocalizations.of(context)!.blockingIpv4, ), - errorText: ipv6error, - helperText: AppLocalizations.of(context)!.blockingIpv6Description, - helperMaxLines: 10, - labelText: AppLocalizations.of(context)!.blockingIpv6, + keyboardType: TextInputType.number, ), - keyboardType: TextInputType.number, ), - ), - const SizedBox(height: 30) - ] - ], + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextFormField( + controller: ipv6controller, + onChanged: validateIpv6, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.link_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv6error, + helperText: AppLocalizations.of(context)!.blockingIpv6Description, + helperMaxLines: 10, + labelText: AppLocalizations.of(context)!.blockingIpv6, + ), + keyboardType: TextInputType.number, + ), + ), + const SizedBox(height: 30) + ] + ], + ), ), ); } diff --git a/lib/screens/settings/dns/private_reverse_servers.dart b/lib/screens/settings/dns/private_reverse_servers.dart index f91c593..a9419e9 100644 --- a/lib/screens/settings/dns/private_reverse_servers.dart +++ b/lib/screens/settings/dns/private_reverse_servers.dart @@ -13,7 +13,7 @@ import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class PrivateReverseDnsServersScreen extends StatefulWidget { - const PrivateReverseDnsServersScreen({Key? key}) : super(key: key); + const PrivateReverseDnsServersScreen({super.key}); @override State createState() => _PrivateReverseDnsServersScreenState(); @@ -149,107 +149,40 @@ class _PrivateReverseDnsServersScreenState extends State item).join(', ').toString().replaceAll(RegExp(r'\(|\)'), '')}", - textAlign: TextAlign.center, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontSize: 16 + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Icon( + Icons.info_rounded, + color: Theme.of(context).listTileTheme.iconColor, + ), + const SizedBox(width: 16), + Flexible( + child: Text( + AppLocalizations.of(context)!.privateReverseDnsServersDescription, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) + ) + ], ), ), ), - Padding( - padding: const EdgeInsets.only(top: 10, bottom: 20), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton.icon( - onPressed: () { - setState(() => editReverseResolvers = true); - checkDataValid(); - }, - icon: const Icon(Icons.edit), - label: Text(AppLocalizations.of(context)!.edit) - ), - ], - ), - ) - ], - if (editReverseResolvers == true) ...[ - const SizedBox(height: 20), - ...reverseResolversControllers.map((c) => Padding( - padding: const EdgeInsets.only( - left: 16, right: 6, bottom: 20 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: TextFormField( - controller: c['controller'], - onChanged: (value) => validateAddress(c, value), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.dns_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: c['error'], - labelText: AppLocalizations.of(context)!.serverAddress, - ) - ), - ), - const SizedBox(width: 8), - IconButton( - onPressed: () { - setState(() => reverseResolversControllers = reverseResolversControllers.where((con) => con != c).toList()); - checkDataValid(); - }, - icon: const Icon(Icons.remove_circle_outline) - ) - ], - ), - )), - if (reverseResolversControllers.isEmpty) Padding( - padding: const EdgeInsets.only( - left: 20, right: 20, bottom: 20 - ), - child: Center( + if (editReverseResolvers == false) ...[ + Padding( + padding: const EdgeInsets.all(20), child: Text( - AppLocalizations.of(context)!.noServerAddressesAdded, + "${AppLocalizations.of(context)!.reverseDnsDefault}:\n\n${defaultReverseResolvers.map((item) => item).join(', ').toString().replaceAll(RegExp(r'\(|\)'), '')}", textAlign: TextAlign.center, style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, @@ -257,41 +190,110 @@ class _PrivateReverseDnsServersScreenState extends State reverseResolversControllers.add({ - 'controller': TextEditingController(), - 'error': null - })); - checkDataValid(); - }, - icon: const Icon(Icons.add), - label: Text(AppLocalizations.of(context)!.addItem) + Padding( + padding: const EdgeInsets.only(top: 10, bottom: 20), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton.icon( + onPressed: () { + setState(() => editReverseResolvers = true); + checkDataValid(); + }, + icon: const Icon(Icons.edit), + label: Text(AppLocalizations.of(context)!.edit) + ), + ], + ), + ) + ], + if (editReverseResolvers == true) ...[ + const SizedBox(height: 20), + ...reverseResolversControllers.map((c) => Padding( + padding: const EdgeInsets.only( + left: 16, right: 6, bottom: 20 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: TextFormField( + controller: c['controller'], + onChanged: (value) => validateAddress(c, value), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.dns_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: c['error'], + labelText: AppLocalizations.of(context)!.serverAddress, + ) + ), + ), + const SizedBox(width: 8), + IconButton( + onPressed: () { + setState(() => reverseResolversControllers = reverseResolversControllers.where((con) => con != c).toList()); + checkDataValid(); + }, + icon: const Icon(Icons.remove_circle_outline) + ) + ], + ), + )), + if (reverseResolversControllers.isEmpty) Padding( + padding: const EdgeInsets.only( + left: 20, right: 20, bottom: 20 + ), + child: Center( + child: Text( + AppLocalizations.of(context)!.noServerAddressesAdded, + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 16 + ), ), - ], + ), ), + Padding( + padding: const EdgeInsets.only(bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton.icon( + onPressed: () { + setState(() => reverseResolversControllers.add({ + 'controller': TextEditingController(), + 'error': null + })); + checkDataValid(); + }, + icon: const Icon(Icons.add), + label: Text(AppLocalizations.of(context)!.addItem) + ), + ], + ), + ), + ], + CustomSwitchListTile( + value: usePrivateReverseDnsResolvers, + onChanged: (value) => setState(() => usePrivateReverseDnsResolvers = value), + title: AppLocalizations.of(context)!.usePrivateReverseDnsResolvers, + subtitle: AppLocalizations.of(context)!.usePrivateReverseDnsResolversDescription + ), + CustomSwitchListTile( + value: enableReverseResolve, + onChanged: (value) => setState(() => enableReverseResolve = value), + title: AppLocalizations.of(context)!.enableReverseResolving, + subtitle: AppLocalizations.of(context)!.enableReverseResolvingDescription ), ], - CustomSwitchListTile( - value: usePrivateReverseDnsResolvers, - onChanged: (value) => setState(() => usePrivateReverseDnsResolvers = value), - title: AppLocalizations.of(context)!.usePrivateReverseDnsResolvers, - subtitle: AppLocalizations.of(context)!.usePrivateReverseDnsResolversDescription - ), - CustomSwitchListTile( - value: enableReverseResolve, - onChanged: (value) => setState(() => enableReverseResolve = value), - title: AppLocalizations.of(context)!.enableReverseResolving, - subtitle: AppLocalizations.of(context)!.enableReverseResolvingDescription - ), - ], + ), ), ); } diff --git a/lib/screens/settings/dns/upstream_dns.dart b/lib/screens/settings/dns/upstream_dns.dart index 5dd2acb..f952110 100644 --- a/lib/screens/settings/dns/upstream_dns.dart +++ b/lib/screens/settings/dns/upstream_dns.dart @@ -189,129 +189,131 @@ class _UpstreamDnsScreenState extends State { const SizedBox(width: 10) ], ), - body: ListView( - padding: const EdgeInsets.only(top: 10), - children: [ - if (dnsServers.isEmpty) Column( - children: [ - Padding( - padding: const EdgeInsets.all(10), - child: Center( - child: Text( - AppLocalizations.of(context)!.noUpstreamDns, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontSize: 16 + body: SafeArea( + child: ListView( + padding: const EdgeInsets.only(top: 10), + children: [ + if (dnsServers.isEmpty) Column( + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: Center( + child: Text( + AppLocalizations.of(context)!.noUpstreamDns, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 16 + ), ), ), ), - ), - const SizedBox(height: 20), - ], - ), - ...dnsServers.map((item) => Padding( - padding: const EdgeInsets.only( - left: 16, right: 6, bottom: 24 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (item['controller'] != null) Expanded( - child: TextFormField( - controller: item['controller'], - onChanged: (_) => checkValidValues(), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.dns_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.dnsServer, - ) - ), - ), - const SizedBox(width: 8), - if (item['comment'] != null) Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - item['comment'], - style: TextStyle( - fontSize: 16, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - IconButton( - onPressed: () => openEditCommentModal(item, dnsServers.indexOf(item)), - icon: const Icon(Icons.edit), - tooltip: AppLocalizations.of(context)!.edit, - ) - ], - ), - ), - IconButton( - onPressed: () { - setState(() => dnsServers = dnsServers.where((i) => i != item).toList()); - checkValidValues(); - }, - icon: const Icon(Icons.remove_circle_outline), - tooltip: AppLocalizations.of(context)!.remove, - ), - const SizedBox(width: 4), + const SizedBox(height: 20), ], ), - )).toList(), - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - mainAxisSize: MainAxisSize.min, - children: [ - ElevatedButton.icon( - onPressed: openAddCommentModal, - icon: const Icon(Icons.add), - label: Text(AppLocalizations.of(context)!.comment) + ...dnsServers.map((item) => Padding( + padding: const EdgeInsets.only( + left: 16, right: 6, bottom: 24 ), - ElevatedButton.icon( - onPressed: () { - setState(() => dnsServers.add({ - 'controller': TextEditingController() - })); - checkValidValues(); - }, - icon: const Icon(Icons.add), - label: Text(AppLocalizations.of(context)!.address) + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (item['controller'] != null) Expanded( + child: TextFormField( + controller: item['controller'], + onChanged: (_) => checkValidValues(), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.dns_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.dnsServer, + ) + ), + ), + const SizedBox(width: 8), + if (item['comment'] != null) Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + item['comment'], + style: TextStyle( + fontSize: 16, + color: Theme.of(context).listTileTheme.iconColor + ), + ), + IconButton( + onPressed: () => openEditCommentModal(item, dnsServers.indexOf(item)), + icon: const Icon(Icons.edit), + tooltip: AppLocalizations.of(context)!.edit, + ) + ], + ), + ), + IconButton( + onPressed: () { + setState(() => dnsServers = dnsServers.where((i) => i != item).toList()); + checkValidValues(); + }, + icon: const Icon(Icons.remove_circle_outline), + tooltip: AppLocalizations.of(context)!.remove, + ), + const SizedBox(width: 4), + ], ), - ], - ), - const SizedBox(height: 16), - SectionLabel(label: AppLocalizations.of(context)!.dnsMode), - CustomRadioListTile( - groupValue: upstreamMode, - value: "", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: AppLocalizations.of(context)!.loadBalancing, - subtitle: AppLocalizations.of(context)!.loadBalancingDescription, - onChanged: (value) => setState(() => upstreamMode = value), - ), - CustomRadioListTile( - groupValue: upstreamMode, - value: "parallel", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: AppLocalizations.of(context)!.parallelRequests, - subtitle: AppLocalizations.of(context)!.parallelRequestsDescription, - onChanged: (value) => setState(() => upstreamMode = value), - ), - CustomRadioListTile( - groupValue: upstreamMode, - value: "fastest_addr", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: AppLocalizations.of(context)!.fastestIpAddress, - subtitle: AppLocalizations.of(context)!.fastestIpAddressDescription, - onChanged: (value) => setState(() => upstreamMode = value), - ), - ], + )).toList(), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton.icon( + onPressed: openAddCommentModal, + icon: const Icon(Icons.add), + label: Text(AppLocalizations.of(context)!.comment) + ), + ElevatedButton.icon( + onPressed: () { + setState(() => dnsServers.add({ + 'controller': TextEditingController() + })); + checkValidValues(); + }, + icon: const Icon(Icons.add), + label: Text(AppLocalizations.of(context)!.address) + ), + ], + ), + const SizedBox(height: 16), + SectionLabel(label: AppLocalizations.of(context)!.dnsMode), + CustomRadioListTile( + groupValue: upstreamMode, + value: "", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.loadBalancing, + subtitle: AppLocalizations.of(context)!.loadBalancingDescription, + onChanged: (value) => setState(() => upstreamMode = value), + ), + CustomRadioListTile( + groupValue: upstreamMode, + value: "parallel", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.parallelRequests, + subtitle: AppLocalizations.of(context)!.parallelRequestsDescription, + onChanged: (value) => setState(() => upstreamMode = value), + ), + CustomRadioListTile( + groupValue: upstreamMode, + value: "fastest_addr", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.fastestIpAddress, + subtitle: AppLocalizations.of(context)!.fastestIpAddressDescription, + onChanged: (value) => setState(() => upstreamMode = value), + ), + ], + ), ), ); } diff --git a/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart b/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart index fcd62d7..439455c 100644 --- a/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart +++ b/lib/screens/settings/dns_rewrites/dns_rewrite_modal.dart @@ -46,10 +46,12 @@ class DnsRewriteModal extends StatelessWidget { ), color: Theme.of(context).dialogBackgroundColor, ), - child: _Content( - onConfirm: onConfirm, - onDelete: onDelete, - rule: rule, + child: SafeArea( + child: _Content( + onConfirm: onConfirm, + onDelete: onDelete, + rule: rule, + ), ) ), ); diff --git a/lib/screens/settings/dns_rewrites/dns_rewrites.dart b/lib/screens/settings/dns_rewrites/dns_rewrites.dart index 8b5ee0d..301c3a1 100644 --- a/lib/screens/settings/dns_rewrites/dns_rewrites.dart +++ b/lib/screens/settings/dns_rewrites/dns_rewrites.dart @@ -136,239 +136,241 @@ class _DnsRewritesScreenState extends State { surfaceTintColor: isDesktop(width) ? Colors.transparent : null, centerTitle: false, ), - body: Stack( - children: [ - Builder( - builder: (context) { - switch (rewriteRulesProvider.loadStatus) { - case LoadStatus.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)!.loadingRewriteRules, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - if (rewriteRulesProvider.rewriteRules!.isNotEmpty) { - return RefreshIndicator( - onRefresh: () async { - final result = await rewriteRulesProvider.fetchRules(); - if (result == false) { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.rewriteRulesNotLoaded, - color: Colors.red - ); - } - }, - child: ListView.builder( - controller: scrollController, - padding: const EdgeInsets.only(top: 0), - itemCount: rewriteRulesProvider.rewriteRules!.length, - itemBuilder: (context, index) => Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: InkWell( - onTap: () => { - if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => DnsRewriteModal( - onConfirm: updateRewriteRule, - dialog: true, - rule: rewriteRulesProvider.rewriteRules![index], - onDelete: (rule) => showDialog( - context: context, - builder: (context) => DeleteDnsRewrite( - onConfirm: () => deleteDnsRewrite(rule) - ) - ), - ), - ) - } - else { - showModalBottomSheet( - context: context, - useRootNavigator: true, - builder: (context) => DnsRewriteModal( - onConfirm: updateRewriteRule, - dialog: false, - rule: rewriteRulesProvider.rewriteRules![index], - onDelete: (rule) => showDialog( - context: context, - builder: (context) => DeleteDnsRewrite( - onConfirm: () => deleteDnsRewrite(rule) - ) - ), - ), - backgroundColor: Colors.transparent, - isScrollControlled: true, - ) - } - }, - borderRadius: BorderRadius.circular(10), - child: Padding( - padding: const EdgeInsets.only( - left: 16, top: 16, bottom: 16, right: 8 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - "${AppLocalizations.of(context)!.domain}: ", - style: TextStyle( - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Text( - rewriteRulesProvider.rewriteRules![index].domain, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], + body: SafeArea( + child: Stack( + children: [ + Builder( + builder: (context) { + switch (rewriteRulesProvider.loadStatus) { + case LoadStatus.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)!.loadingRewriteRules, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + case LoadStatus.loaded: + if (rewriteRulesProvider.rewriteRules!.isNotEmpty) { + return RefreshIndicator( + onRefresh: () async { + final result = await rewriteRulesProvider.fetchRules(); + if (result == false) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.rewriteRulesNotLoaded, + color: Colors.red + ); + } + }, + child: ListView.builder( + controller: scrollController, + padding: const EdgeInsets.only(top: 0), + itemCount: rewriteRulesProvider.rewriteRules!.length, + itemBuilder: (context, index) => Card( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: InkWell( + onTap: () => { + if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (context) => DnsRewriteModal( + onConfirm: updateRewriteRule, + dialog: true, + rule: rewriteRulesProvider.rewriteRules![index], + onDelete: (rule) => showDialog( + context: context, + builder: (context) => DeleteDnsRewrite( + onConfirm: () => deleteDnsRewrite(rule) + ) ), - const SizedBox(height: 3), - Row( - children: [ - Text( - "${AppLocalizations.of(context)!.answer}: ", - style: TextStyle( - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Text( - rewriteRulesProvider.rewriteRules![index].answer, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ), - ], - ), - Icon( - Icons.keyboard_arrow_right_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant, + ), ) - ], + } + else { + showModalBottomSheet( + context: context, + useRootNavigator: true, + builder: (context) => DnsRewriteModal( + onConfirm: updateRewriteRule, + dialog: false, + rule: rewriteRulesProvider.rewriteRules![index], + onDelete: (rule) => showDialog( + context: context, + builder: (context) => DeleteDnsRewrite( + onConfirm: () => deleteDnsRewrite(rule) + ) + ), + ), + backgroundColor: Colors.transparent, + isScrollControlled: true, + ) + } + }, + borderRadius: BorderRadius.circular(10), + child: Padding( + padding: const EdgeInsets.only( + left: 16, top: 16, bottom: 16, right: 8 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + "${AppLocalizations.of(context)!.domain}: ", + style: TextStyle( + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + Text( + rewriteRulesProvider.rewriteRules![index].domain, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + const SizedBox(height: 3), + Row( + children: [ + Text( + "${AppLocalizations.of(context)!.answer}: ", + style: TextStyle( + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + Text( + rewriteRulesProvider.rewriteRules![index].answer, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ], + ), + Icon( + Icons.keyboard_arrow_right_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ) + ], + ), ), ), - ), - ) - ), - ); - } - else { - return Center( - child: Text( - AppLocalizations.of(context)!.noRewriteRules, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, + ) ), - ), - ); - } - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.rewriteRulesNotLoaded, - textAlign: TextAlign.center, + ); + } + else { + return Center( + child: Text( + AppLocalizations.of(context)!.noRewriteRules, style: TextStyle( fontSize: 22, color: Theme.of(context).colorScheme.onSurfaceVariant, ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - }, - ), - AnimatedPositioned( - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - bottom: isVisible ? - appConfigProvider.showingSnackbar - ? 70 : 20 - : -70, - right: 20, - child: FloatingActionButton( - onPressed: () => { - if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => DnsRewriteModal( - onConfirm: addDnsRewrite, - dialog: true, - onDelete: (rule) => showDialog( - context: context, - builder: (context) => DeleteDnsRewrite( - onConfirm: () => deleteDnsRewrite(rule) - ) ), - ), - ) - } - else { - showModalBottomSheet( - context: context, - useRootNavigator: true, - builder: (context) => DnsRewriteModal( - onConfirm: addDnsRewrite, - dialog: false, - onDelete: (rule) => showDialog( - context: context, - builder: (context) => DeleteDnsRewrite( - onConfirm: () => deleteDnsRewrite(rule) + ); + } + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.rewriteRulesNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), ) - ), + ], ), - backgroundColor: Colors.transparent, - isScrollControlled: true - ) - } - }, - child: const Icon(Icons.add), - ), - ) - ], + ); + + default: + return const SizedBox(); + } + }, + ), + AnimatedPositioned( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + bottom: isVisible ? + appConfigProvider.showingSnackbar + ? 70 : 20 + : -70, + right: 20, + child: FloatingActionButton( + onPressed: () => { + if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (context) => DnsRewriteModal( + onConfirm: addDnsRewrite, + dialog: true, + onDelete: (rule) => showDialog( + context: context, + builder: (context) => DeleteDnsRewrite( + onConfirm: () => deleteDnsRewrite(rule) + ) + ), + ), + ) + } + else { + showModalBottomSheet( + context: context, + useRootNavigator: true, + builder: (context) => DnsRewriteModal( + onConfirm: addDnsRewrite, + dialog: false, + onDelete: (rule) => showDialog( + context: context, + builder: (context) => DeleteDnsRewrite( + onConfirm: () => deleteDnsRewrite(rule) + ) + ), + ), + backgroundColor: Colors.transparent, + isScrollControlled: true + ) + } + }, + child: const Icon(Icons.add), + ), + ) + ], + ), ), ); } diff --git a/lib/screens/settings/dns_rewrites/rule_modal.dart b/lib/screens/settings/dns_rewrites/rule_modal.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/screens/settings/dns_rewrites/server_version_needed.dart b/lib/screens/settings/dns_rewrites/server_version_needed.dart index 88888c6..68ea426 100644 --- a/lib/screens/settings/dns_rewrites/server_version_needed.dart +++ b/lib/screens/settings/dns_rewrites/server_version_needed.dart @@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class ServerVersionNeeded extends StatelessWidget { final String version; + // ignore: use_super_parameters const ServerVersionNeeded({ Key? key, required this.version diff --git a/lib/screens/settings/encryption/encryption.dart b/lib/screens/settings/encryption/encryption.dart index f47a63d..e11047e 100644 --- a/lib/screens/settings/encryption/encryption.dart +++ b/lib/screens/settings/encryption/encryption.dart @@ -283,376 +283,378 @@ class _EncryptionSettingsState extends State { const SizedBox(width: 10), ], ), - body: Builder( - builder: (context) { - switch (loadStatus) { - case LoadStatus.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)!.loadingEncryptionSettings, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ) - ); - - case LoadStatus.loaded: - return ListView( - children: [ - EncryptionMasterSwitch( - value: enabled, - onChange: (value) { - setState(() => enabled = value); - onEditValidate(); - } - ), - SectionLabel( - label: AppLocalizations.of(context)!.serverConfiguration, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - ), - EncryptionTextField( - enabled: enabled, - controller: domainNameController, - icon: Icons.link_rounded, - onChanged: (value) { - setState(() => domainError = validateDomain(context, value)); - onEditValidate(); - }, - errorText: domainError, - label: AppLocalizations.of(context)!.domainName, - helperText: AppLocalizations.of(context)!.domainNameDescription, - ), - const SizedBox(height: 10), - CustomSwitchListTile( - value: redirectHttps, - onChanged: (value) { - setState(() => redirectHttps = value); - onEditValidate(); - }, - title: AppLocalizations.of(context)!.redirectHttps, - disabled: !enabled, - ), - const SizedBox(height: 10), - Wrap( + body: SafeArea( + child: Builder( + builder: (context) { + switch (loadStatus) { + case LoadStatus.loading: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - FractionallySizedBox( - widthFactor: width > 900 ? 0.33 : 1, - child: EncryptionTextField( - enabled: enabled, - controller: httpsPortController, - icon: Icons.numbers_rounded, - onChanged: (value) { - setState(() => httpsPortError = validatePort(context, value)); - onEditValidate(); - }, - errorText: httpsPortError, - label: AppLocalizations.of(context)!.httpsPort, - keyboardType: TextInputType.number, + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingEncryptionSettings, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), - ), - Padding( - padding: width <= 900 - ? const EdgeInsets.symmetric(vertical: 24) - : const EdgeInsets.all(0), - child: FractionallySizedBox( + ) + ], + ) + ); + + case LoadStatus.loaded: + return ListView( + children: [ + EncryptionMasterSwitch( + value: enabled, + onChange: (value) { + setState(() => enabled = value); + onEditValidate(); + } + ), + SectionLabel( + label: AppLocalizations.of(context)!.serverConfiguration, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + ), + EncryptionTextField( + enabled: enabled, + controller: domainNameController, + icon: Icons.link_rounded, + onChanged: (value) { + setState(() => domainError = validateDomain(context, value)); + onEditValidate(); + }, + errorText: domainError, + label: AppLocalizations.of(context)!.domainName, + helperText: AppLocalizations.of(context)!.domainNameDescription, + ), + const SizedBox(height: 10), + CustomSwitchListTile( + value: redirectHttps, + onChanged: (value) { + setState(() => redirectHttps = value); + onEditValidate(); + }, + title: AppLocalizations.of(context)!.redirectHttps, + disabled: !enabled, + ), + const SizedBox(height: 10), + Wrap( + children: [ + FractionallySizedBox( widthFactor: width > 900 ? 0.33 : 1, child: EncryptionTextField( enabled: enabled, - controller: tlsPortController, + controller: httpsPortController, icon: Icons.numbers_rounded, onChanged: (value) { - setState(() => tlsPortError = validatePort(context, value)); + setState(() => httpsPortError = validatePort(context, value)); onEditValidate(); }, - errorText: tlsPortError, - label: AppLocalizations.of(context)!.tlsPort, + errorText: httpsPortError, + label: AppLocalizations.of(context)!.httpsPort, keyboardType: TextInputType.number, ), ), - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.33 : 1, - child: EncryptionTextField( - enabled: enabled, - controller: dnsOverQuicPortController, - icon: Icons.numbers_rounded, - onChanged: (value) { - setState(() => dnsOverQuicPortError = validatePort(context, value)); - onEditValidate(); - }, - errorText: dnsOverQuicPortError, - label: AppLocalizations.of(context)!.dnsOverQuicPort, - keyboardType: TextInputType.number, + Padding( + padding: width <= 900 + ? const EdgeInsets.symmetric(vertical: 24) + : const EdgeInsets.all(0), + child: FractionallySizedBox( + widthFactor: width > 900 ? 0.33 : 1, + child: EncryptionTextField( + enabled: enabled, + controller: tlsPortController, + icon: Icons.numbers_rounded, + onChanged: (value) { + setState(() => tlsPortError = validatePort(context, value)); + onEditValidate(); + }, + errorText: tlsPortError, + label: AppLocalizations.of(context)!.tlsPort, + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.33 : 1, + child: EncryptionTextField( + enabled: enabled, + controller: dnsOverQuicPortController, + icon: Icons.numbers_rounded, + onChanged: (value) { + setState(() => dnsOverQuicPortError = validatePort(context, value)); + onEditValidate(); + }, + errorText: dnsOverQuicPortError, + label: AppLocalizations.of(context)!.dnsOverQuicPort, + keyboardType: TextInputType.number, + ), + ), + ], + ), + SectionLabel( + label: AppLocalizations.of(context)!.certificates, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + ), + Card( + margin: const EdgeInsets.symmetric(horizontal: 16), + child: Padding( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + Icon( + Icons.info_rounded, + color: Theme.of(context).listTileTheme.iconColor, + ), + const SizedBox(width: 20), + Flexible( + child: Text( + AppLocalizations.of(context)!.certificatesDescription, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + ), + ) + ) + ], ), ), - ], - ), - SectionLabel( - label: AppLocalizations.of(context)!.certificates, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - ), - Card( - margin: const EdgeInsets.symmetric(horizontal: 16), - child: Padding( - padding: const EdgeInsets.all(20), - child: Row( - children: [ - Icon( - Icons.info_rounded, - color: Theme.of(context).listTileTheme.iconColor, - ), - const SizedBox(width: 20), - Flexible( - child: Text( - AppLocalizations.of(context)!.certificatesDescription, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - ), - ) - ) - ], - ), ), - ), - const SizedBox(height: 20), - RadioListTile( - value: 0, - groupValue: certificateOption, - onChanged: enabled == true - ? (value) { - setState(() => certificateOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.certificateFilePath, - style: const TextStyle( - fontWeight: FontWeight.normal - ), - ), - ), - RadioListTile( - value: 1, - groupValue: certificateOption, - onChanged: enabled == true - ? (value) { - setState(() => certificateOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.pasteCertificateContent, - style: const TextStyle( - fontWeight: FontWeight.normal - ), - ), - ), - const SizedBox(height: 10), - if (certificateOption == 0) EncryptionTextField( - enabled: enabled, - controller: certificatePathController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => certificatePathError = validatePath(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.certificatePath, - errorText: certificatePathError, - ), - if (certificateOption == 1) EncryptionTextField( - enabled: enabled, - controller: certificateContentController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => certificateContentError = validateCertificate(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.certificateContent, - errorText: certificateContentError, - multiline: true, - keyboardType: TextInputType.multiline, - ), - if (certKeyValid != null && (certificateContentController.text != '' || certificatePathController.text != '')) ...[ const SizedBox(height: 20), - if (certKeyValid!.validChain != null) ...[ - Status( - valid: certKeyValid!.validChain ?? false, - label: certKeyValid!.validChain == true - ? AppLocalizations.of(context)!.validCertificateChain - : AppLocalizations.of(context)!.invalidCertificateChain, + RadioListTile( + value: 0, + groupValue: certificateOption, + onChanged: enabled == true + ? (value) { + setState(() => certificateOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.certificateFilePath, + style: const TextStyle( + fontWeight: FontWeight.normal + ), ), - const SizedBox(height: 10), - ], - if (certKeyValid!.subject != null) ...[ - Status( + ), + RadioListTile( + value: 1, + groupValue: certificateOption, + onChanged: enabled == true + ? (value) { + setState(() => certificateOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.pasteCertificateContent, + style: const TextStyle( + fontWeight: FontWeight.normal + ), + ), + ), + const SizedBox(height: 10), + if (certificateOption == 0) EncryptionTextField( + enabled: enabled, + controller: certificatePathController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => certificatePathError = validatePath(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.certificatePath, + errorText: certificatePathError, + ), + if (certificateOption == 1) EncryptionTextField( + enabled: enabled, + controller: certificateContentController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => certificateContentError = validateCertificate(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.certificateContent, + errorText: certificateContentError, + multiline: true, + keyboardType: TextInputType.multiline, + ), + if (certKeyValid != null && (certificateContentController.text != '' || certificatePathController.text != '')) ...[ + const SizedBox(height: 20), + if (certKeyValid!.validChain != null) ...[ + Status( + valid: certKeyValid!.validChain ?? false, + label: certKeyValid!.validChain == true + ? AppLocalizations.of(context)!.validCertificateChain + : AppLocalizations.of(context)!.invalidCertificateChain, + ), + const SizedBox(height: 10), + ], + if (certKeyValid!.subject != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.subject}: ${certKeyValid?.subject}" + ), + const SizedBox(height: 10), + ], + if (certKeyValid!.issuer != null) ...[ + Status( valid: true, - label: "${AppLocalizations.of(context)!.subject}: ${certKeyValid?.subject}" + label: "${AppLocalizations.of(context)!.issuer}: ${certKeyValid?.issuer}" ), - const SizedBox(height: 10), + const SizedBox(height: 10), + ], + if (certKeyValid!.notAfter != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.expirationDate}: ${certKeyValid?.notAfter}" + ), + const SizedBox(height: 10), + ], + if (certKeyValid!.dnsNames != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.hostNames}: ${certKeyValid?.dnsNames?.join(', ')}" + ), + const SizedBox(height: 10), + ], ], - if (certKeyValid!.issuer != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.issuer}: ${certKeyValid?.issuer}" + SectionLabel( + label: AppLocalizations.of(context)!.privateKey, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), ), - const SizedBox(height: 10), - ], - if (certKeyValid!.notAfter != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.expirationDate}: ${certKeyValid?.notAfter}" - ), - const SizedBox(height: 10), - ], - if (certKeyValid!.dnsNames != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.hostNames}: ${certKeyValid?.dnsNames?.join(', ')}" - ), - const SizedBox(height: 10), - ], - ], - SectionLabel( - label: AppLocalizations.of(context)!.privateKey, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - ), - RadioListTile( - value: 0, - groupValue: privateKeyOption, - onChanged: enabled == true - ? (value) { - setState(() => privateKeyOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.privateKeyFile, - style: const TextStyle( - fontWeight: FontWeight.normal + RadioListTile( + value: 0, + groupValue: privateKeyOption, + onChanged: enabled == true + ? (value) { + setState(() => privateKeyOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.privateKeyFile, + style: const TextStyle( + fontWeight: FontWeight.normal + ), ), ), - ), - RadioListTile( - value: 1, - groupValue: privateKeyOption, - onChanged: enabled == true - ? (value) { - setState(() => privateKeyOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.pastePrivateKey, - style: const TextStyle( - fontWeight: FontWeight.normal + RadioListTile( + value: 1, + groupValue: privateKeyOption, + onChanged: enabled == true + ? (value) { + setState(() => privateKeyOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.pastePrivateKey, + style: const TextStyle( + fontWeight: FontWeight.normal + ), ), ), - ), - if (privateKeyOption == 0) const SizedBox(height: 10), - if (privateKeyOption == 1) ...[ - CustomSwitchListTile( - value: usePreviouslySavedKey, - onChanged: (value) => setState(() => usePreviouslySavedKey = value), - title: AppLocalizations.of(context)!.usePreviousKey, - ), - const SizedBox(height: 10) - ], - if (privateKeyOption == 0) EncryptionTextField( - enabled: enabled, - controller: privateKeyPathController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => privateKeyPathError = validatePath(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.privateKeyPath, - errorText: privateKeyPathError, - ), - if (privateKeyOption == 1) EncryptionTextField( - enabled: enabled == true - ? !usePreviouslySavedKey - : false, - controller: pastePrivateKeyController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => pastePrivateKeyError = validatePrivateKey(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.pastePrivateKey, - errorText: pastePrivateKeyError, - keyboardType: TextInputType.multiline, - multiline: true, - ), - const SizedBox(height: 20), - if (certKeyValid != null && (privateKeyPathController.text != '' || pastePrivateKeyController.text != '' || usePreviouslySavedKey == true)) ...[ - if (certKeyValid!.validKey != null) ...[ - Status( - valid: certKeyValid!.validKey ?? false, - label: certKeyValid!.validKey == true - ? AppLocalizations.of(context)!.validPrivateKey - : AppLocalizations.of(context)!.invalidPrivateKey, + if (privateKeyOption == 0) const SizedBox(height: 10), + if (privateKeyOption == 1) ...[ + CustomSwitchListTile( + value: usePreviouslySavedKey, + onChanged: (value) => setState(() => usePreviouslySavedKey = value), + title: AppLocalizations.of(context)!.usePreviousKey, ), const SizedBox(height: 10) ], - if (certKeyValid!.validPair != null && certKeyValid!.validPair == false) ...[ - Status( - valid: false, - label: AppLocalizations.of(context)!.keysNotMatch, - ), - const SizedBox(height: 10) - ], - if (certKeyValid!.keyType != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.keyType}: ${certKeyValid!.keyType}" - ), - const SizedBox(height: 10), - ], - const SizedBox(height: 10) - ] - ], - ); - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, + if (privateKeyOption == 0) EncryptionTextField( + enabled: enabled, + controller: privateKeyPathController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => privateKeyPathError = validatePath(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.privateKeyPath, + errorText: privateKeyPathError, ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.encryptionSettingsNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) + if (privateKeyOption == 1) EncryptionTextField( + enabled: enabled == true + ? !usePreviouslySavedKey + : false, + controller: pastePrivateKeyController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => pastePrivateKeyError = validatePrivateKey(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.pastePrivateKey, + errorText: pastePrivateKeyError, + keyboardType: TextInputType.multiline, + multiline: true, + ), + const SizedBox(height: 20), + if (certKeyValid != null && (privateKeyPathController.text != '' || pastePrivateKeyController.text != '' || usePreviouslySavedKey == true)) ...[ + if (certKeyValid!.validKey != null) ...[ + Status( + valid: certKeyValid!.validKey ?? false, + label: certKeyValid!.validKey == true + ? AppLocalizations.of(context)!.validPrivateKey + : AppLocalizations.of(context)!.invalidPrivateKey, + ), + const SizedBox(height: 10) + ], + if (certKeyValid!.validPair != null && certKeyValid!.validPair == false) ...[ + Status( + valid: false, + label: AppLocalizations.of(context)!.keysNotMatch, + ), + const SizedBox(height: 10) + ], + if (certKeyValid!.keyType != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.keyType}: ${certKeyValid!.keyType}" + ), + const SizedBox(height: 10), + ], + const SizedBox(height: 10) + ] ], - ), - ); - - default: - return const SizedBox(); - } - }, + ); + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.encryptionSettingsNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, + ), ) ); } diff --git a/lib/screens/settings/general_settings/general_settings.dart b/lib/screens/settings/general_settings/general_settings.dart index 20e13e8..74cc752 100644 --- a/lib/screens/settings/general_settings/general_settings.dart +++ b/lib/screens/settings/general_settings/general_settings.dart @@ -124,173 +124,175 @@ class _GeneralSettingsState extends State { title: Text(AppLocalizations.of(context)!.generalSettings), surfaceTintColor: isDesktop(width) ? Colors.transparent : null, ), - body: ListView( - children: [ - SectionLabel(label: AppLocalizations.of(context)!.home), - CustomListTile( - icon: Icons.exposure_zero_rounded, - title: AppLocalizations.of(context)!.hideZeroValues, - subtitle: AppLocalizations.of(context)!.hideZeroValuesDescription, - trailing: Switch( - value: appConfigProvider.hideZeroValues, - onChanged: (value) => updateSettings( - newStatus: value, + body: SafeArea( + child: ListView( + children: [ + SectionLabel(label: AppLocalizations.of(context)!.home), + CustomListTile( + icon: Icons.exposure_zero_rounded, + title: AppLocalizations.of(context)!.hideZeroValues, + subtitle: AppLocalizations.of(context)!.hideZeroValuesDescription, + trailing: Switch( + value: appConfigProvider.hideZeroValues, + onChanged: (value) => updateSettings( + newStatus: value, + function: appConfigProvider.setHideZeroValues + ), + ), + onTap: () => updateSettings( + newStatus: !appConfigProvider.hideZeroValues, function: appConfigProvider.setHideZeroValues ), + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 16, + right: 10 + ) ), - onTap: () => updateSettings( - newStatus: !appConfigProvider.hideZeroValues, - function: appConfigProvider.setHideZeroValues - ), - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - left: 16, - right: 10 - ) - ), - CustomListTile( - icon: Icons.show_chart_rounded, - title: AppLocalizations.of(context)!.combinedChart, - subtitle: AppLocalizations.of(context)!.combinedChartDescription, - trailing: Switch( - value: appConfigProvider.combinedChartHome, - onChanged: (value) => updateSettings( - newStatus: value, + CustomListTile( + icon: Icons.show_chart_rounded, + title: AppLocalizations.of(context)!.combinedChart, + subtitle: AppLocalizations.of(context)!.combinedChartDescription, + trailing: Switch( + value: appConfigProvider.combinedChartHome, + onChanged: (value) => updateSettings( + newStatus: value, + function: appConfigProvider.setCombinedChartHome + ), + ), + onTap: () => updateSettings( + newStatus: !appConfigProvider.combinedChartHome, function: appConfigProvider.setCombinedChartHome ), + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 16, + right: 10 + ) ), - onTap: () => updateSettings( - newStatus: !appConfigProvider.combinedChartHome, - function: appConfigProvider.setCombinedChartHome - ), - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - left: 16, - right: 10 - ) - ), - CustomListTile( - icon: Icons.remove_red_eye_rounded, - title: AppLocalizations.of(context)!.hideServerAddress, - subtitle: AppLocalizations.of(context)!.hideServerAddressDescription, - trailing: Switch( - value: appConfigProvider.hideServerAddress, - onChanged: (value) => updateSettings( - newStatus: value, + CustomListTile( + icon: Icons.remove_red_eye_rounded, + title: AppLocalizations.of(context)!.hideServerAddress, + subtitle: AppLocalizations.of(context)!.hideServerAddressDescription, + trailing: Switch( + value: appConfigProvider.hideServerAddress, + onChanged: (value) => updateSettings( + newStatus: value, + function: appConfigProvider.setHideServerAddress + ), + ), + onTap: () => updateSettings( + newStatus: !appConfigProvider.hideServerAddress, function: appConfigProvider.setHideServerAddress ), + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 16, + right: 10 + ) ), - onTap: () => updateSettings( - newStatus: !appConfigProvider.hideServerAddress, - function: appConfigProvider.setHideServerAddress + CustomListTile( + icon: Icons.reorder_rounded, + title: AppLocalizations.of(context)!.topItemsOrder, + subtitle: AppLocalizations.of(context)!.topItemsOrderDescription, + onTap: () => widget.splitView == true + ? SplitView.of(context).push(const ReorderableTopItemsHome()) + : Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const ReorderableTopItemsHome() + ) + ) ), - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - left: 16, - right: 10 - ) - ), - CustomListTile( - icon: Icons.reorder_rounded, - title: AppLocalizations.of(context)!.topItemsOrder, - subtitle: AppLocalizations.of(context)!.topItemsOrderDescription, - onTap: () => widget.splitView == true - ? SplitView.of(context).push(const ReorderableTopItemsHome()) - : Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const ReorderableTopItemsHome() - ) - ) - ), - CustomListTile( - icon: Icons.donut_large_rounded, - title: AppLocalizations.of(context)!.showTopItemsChart, - subtitle: AppLocalizations.of(context)!.showTopItemsChartDescription, - trailing: Switch( - value: appConfigProvider.showTopItemsChart, - onChanged: (value) => updateSettings( - newStatus: value, + CustomListTile( + icon: Icons.donut_large_rounded, + title: AppLocalizations.of(context)!.showTopItemsChart, + subtitle: AppLocalizations.of(context)!.showTopItemsChartDescription, + trailing: Switch( + value: appConfigProvider.showTopItemsChart, + onChanged: (value) => updateSettings( + newStatus: value, + function: appConfigProvider.setShowTopItemsChart + ), + ), + onTap: () => updateSettings( + newStatus: !appConfigProvider.showTopItemsChart, function: appConfigProvider.setShowTopItemsChart ), + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 16, + right: 10 + ) ), - onTap: () => updateSettings( - newStatus: !appConfigProvider.showTopItemsChart, - function: appConfigProvider.setShowTopItemsChart - ), - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - left: 16, - right: 10 - ) - ), - SectionLabel(label: AppLocalizations.of(context)!.logs), - CustomListTile( - icon: Icons.timer_rounded, - title: AppLocalizations.of(context)!.timeLogs, - subtitle: AppLocalizations.of(context)!.timeLogsDescription, - trailing: Switch( - value: appConfigProvider.showTimeLogs, - onChanged: (value) => updateSettings( - newStatus: value, + SectionLabel(label: AppLocalizations.of(context)!.logs), + CustomListTile( + icon: Icons.timer_rounded, + title: AppLocalizations.of(context)!.timeLogs, + subtitle: AppLocalizations.of(context)!.timeLogsDescription, + trailing: Switch( + value: appConfigProvider.showTimeLogs, + onChanged: (value) => updateSettings( + newStatus: value, + function: appConfigProvider.setshowTimeLogs + ), + ), + onTap: () => updateSettings( + newStatus: !appConfigProvider.showTimeLogs, function: appConfigProvider.setshowTimeLogs ), + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 16, + right: 10 + ) ), - onTap: () => updateSettings( - newStatus: !appConfigProvider.showTimeLogs, - function: appConfigProvider.setshowTimeLogs - ), - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - left: 16, - right: 10 - ) - ), - CustomListTile( - icon: Icons.more, - title: AppLocalizations.of(context)!.ipLogs, - subtitle: AppLocalizations.of(context)!.ipLogsDescription, - trailing: Switch( - value: appConfigProvider.showIpLogs, - onChanged: (value) => updateSettings( - newStatus: value, + CustomListTile( + icon: Icons.more, + title: AppLocalizations.of(context)!.ipLogs, + subtitle: AppLocalizations.of(context)!.ipLogsDescription, + trailing: Switch( + value: appConfigProvider.showIpLogs, + onChanged: (value) => updateSettings( + newStatus: value, + function: appConfigProvider.setShowIpLogs + ), + ), + onTap: () => updateSettings( + newStatus: !appConfigProvider.showIpLogs, function: appConfigProvider.setShowIpLogs ), + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 16, + right: 10 + ) ), - onTap: () => updateSettings( - newStatus: !appConfigProvider.showIpLogs, - function: appConfigProvider.setShowIpLogs - ), - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - left: 16, - right: 10 - ) - ), - if ( - !(Platform.isAndroid || Platform.isIOS) || - (Platform.isAndroid && ( - appConfigProvider.installationSource == Source.IS_INSTALLED_FROM_LOCAL_SOURCE || - appConfigProvider.installationSource == Source.IS_INSTALLED_FROM_PLAY_PACKAGE_INSTALLER || - appConfigProvider.installationSource == Source.UNKNOWN - )) - ) ...[ - SectionLabel(label: AppLocalizations.of(context)!.application), - CustomListTile( - icon: Icons.system_update_rounded, - title: AppLocalizations.of(context)!.appUpdates, - subtitle: appConfigProvider.appUpdatesAvailable != null - ? AppLocalizations.of(context)!.updateAvailable - : AppLocalizations.of(context)!.usingLatestVersion, - trailing: generateAppUpdateStatus() - ) - ] - ], + if ( + !(Platform.isAndroid || Platform.isIOS) || + (Platform.isAndroid && ( + appConfigProvider.installationSource == Source.IS_INSTALLED_FROM_LOCAL_SOURCE || + appConfigProvider.installationSource == Source.IS_INSTALLED_FROM_PLAY_PACKAGE_INSTALLER || + appConfigProvider.installationSource == Source.UNKNOWN + )) + ) ...[ + SectionLabel(label: AppLocalizations.of(context)!.application), + CustomListTile( + icon: Icons.system_update_rounded, + title: AppLocalizations.of(context)!.appUpdates, + subtitle: appConfigProvider.appUpdatesAvailable != null + ? AppLocalizations.of(context)!.updateAvailable + : AppLocalizations.of(context)!.usingLatestVersion, + trailing: generateAppUpdateStatus() + ) + ] + ], + ), ) ); } diff --git a/lib/screens/settings/safe_search_settings.dart b/lib/screens/settings/safe_search_settings.dart index 99f6ad7..793a1c2 100644 --- a/lib/screens/settings/safe_search_settings.dart +++ b/lib/screens/settings/safe_search_settings.dart @@ -14,7 +14,7 @@ import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class SafeSearchSettingsScreen extends StatefulWidget { - const SafeSearchSettingsScreen({Key? key}) : super(key: key); + const SafeSearchSettingsScreen({super.key}); @override State createState() => _SafeSearchSettingsScreenState(); @@ -123,166 +123,168 @@ class _SafeSearchSettingsScreenState extends State { const SizedBox(width: 8) ], ), - body: Builder( - builder: (context) { - switch (statusProvider.loadStatus) { - case LoadStatus.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)!.loadingSafeSearchSettings, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - return RefreshIndicator( - onRefresh: requestSafeSearchSettings, - child: ListView( - children: [ - Padding( - padding: const EdgeInsets.only( - top: 16, - left: 16, - right: 16, - bottom: 8 - ), - child: Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(28), - child: InkWell( + body: SafeArea( + child: Builder( + builder: (context) { + switch (statusProvider.loadStatus) { + case LoadStatus.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)!.loadingSafeSearchSettings, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + case LoadStatus.loaded: + return RefreshIndicator( + onRefresh: requestSafeSearchSettings, + child: ListView( + children: [ + Padding( + padding: const EdgeInsets.only( + top: 16, + left: 16, + right: 16, + bottom: 8 + ), + child: Material( + color: Colors.transparent, borderRadius: BorderRadius.circular(28), - onTap: () => setState(() => generalEnabled = !generalEnabled), - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 12 - ), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(28) - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Text( - AppLocalizations.of(context)!.enableSafeSearch, - style: const TextStyle( - fontSize: 18 + child: InkWell( + borderRadius: BorderRadius.circular(28), + onTap: () => setState(() => generalEnabled = !generalEnabled), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 12 + ), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(28) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + AppLocalizations.of(context)!.enableSafeSearch, + style: const TextStyle( + fontSize: 18 + ), ), ), - ), - Switch( - value: generalEnabled, - onChanged: (value) => setState(() => generalEnabled = value) - ) - ], + Switch( + value: generalEnabled, + onChanged: (value) => setState(() => generalEnabled = value) + ) + ], + ), ), ), ), ), - ), - CustomCheckboxListTile( - value: bingEnabled, - onChanged: (value) => setState(() => bingEnabled = value), - title: "Bing", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 + CustomCheckboxListTile( + value: bingEnabled, + onChanged: (value) => setState(() => bingEnabled = value), + title: "Bing", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, ), - disabled: !generalEnabled, - ), - CustomCheckboxListTile( - value: duckduckgoEnabled, - onChanged: (value) => setState(() => duckduckgoEnabled = value), - title: "DuckDuckGo", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 + CustomCheckboxListTile( + value: duckduckgoEnabled, + onChanged: (value) => setState(() => duckduckgoEnabled = value), + title: "DuckDuckGo", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, ), - disabled: !generalEnabled, - ), - CustomCheckboxListTile( - value: googleEnabled, - onChanged: (value) => setState(() => googleEnabled = value), - title: "Google", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 + CustomCheckboxListTile( + value: googleEnabled, + onChanged: (value) => setState(() => googleEnabled = value), + title: "Google", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, ), - disabled: !generalEnabled, - ), - CustomCheckboxListTile( - value: pixabayEnabled, - onChanged: (value) => setState(() => pixabayEnabled = value), - title: "Pixabay", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 + CustomCheckboxListTile( + value: pixabayEnabled, + onChanged: (value) => setState(() => pixabayEnabled = value), + title: "Pixabay", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, ), - disabled: !generalEnabled, - ), - CustomCheckboxListTile( - value: yandexEnabled, - onChanged: (value) => setState(() => yandexEnabled = value), - title: "Yandex", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 + CustomCheckboxListTile( + value: yandexEnabled, + onChanged: (value) => setState(() => yandexEnabled = value), + title: "Yandex", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, ), - disabled: !generalEnabled, - ), - CustomCheckboxListTile( - value: youtubeEnabled, - onChanged: (value) => setState(() => youtubeEnabled = value), - title: "YouTube", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 + CustomCheckboxListTile( + value: youtubeEnabled, + onChanged: (value) => setState(() => youtubeEnabled = value), + title: "YouTube", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, ), - disabled: !generalEnabled, - ), - ], - ), - ); - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.safeSearchSettingsNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, + ], + ), + ); + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, ), - ) - ], - ), - ); - - - default: - return const SizedBox(); - } - }, + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.safeSearchSettingsNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + + default: + return const SizedBox(); + } + }, + ), ) ); } diff --git a/lib/screens/settings/server_info/server_info.dart b/lib/screens/settings/server_info/server_info.dart index 3032dcf..b15d8e0 100644 --- a/lib/screens/settings/server_info/server_info.dart +++ b/lib/screens/settings/server_info/server_info.dart @@ -51,114 +51,116 @@ class _ServerInformationState extends State { surfaceTintColor: isDesktop(width) ? Colors.transparent : null, centerTitle: false, ), - body: Builder( - builder: (context) { - switch (serverInfo.loadStatus) { - case LoadStatus.loading: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + body: SafeArea( + child: Builder( + builder: (context) { + switch (serverInfo.loadStatus) { + case LoadStatus.loading: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + AppLocalizations.of(context)!.loadingServerInfo, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ) + ], + ), + ); + + case LoadStatus.loaded: + return ListView( children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - AppLocalizations.of(context)!.loadingServerInfo, + CustomListTile( + title: AppLocalizations.of(context)!.dnsAddresses, + subtitle: AppLocalizations.of(context)!.seeDnsAddresses, + onTap: () { + showModal( + context: context, + builder: (context) => DnsAddressesModal( + dnsAddresses: serverInfo.data!.dnsAddresses + ) + ); + }, + ), + CustomListTile( + title: AppLocalizations.of(context)!.dnsPort, + subtitle: serverInfo.data!.dnsPort.toString(), + ), + CustomListTile( + title: AppLocalizations.of(context)!.httpPort, + subtitle: serverInfo.data!.httpPort.toString(), + ), + CustomListTile( + title: AppLocalizations.of(context)!.protectionEnabled, + subtitle: serverInfo.data!.protectionEnabled == true + ? AppLocalizations.of(context)!.yes + : AppLocalizations.of(context)!.no, + ), + CustomListTile( + title: AppLocalizations.of(context)!.dhcpAvailable, + subtitle: serverInfo.data!.dhcpAvailable == true + ? AppLocalizations.of(context)!.yes + : AppLocalizations.of(context)!.no, + ), + CustomListTile( + title: AppLocalizations.of(context)!.serverRunning, + subtitle: serverInfo.data!.running == true + ? AppLocalizations.of(context)!.yes + : AppLocalizations.of(context)!.no, + ), + CustomListTile( + title: AppLocalizations.of(context)!.serverVersion, + subtitle: serverInfo.data!.version, + ), + CustomListTile( + title: AppLocalizations.of(context)!.serverLanguage, + subtitle: serverInfo.data!.language, + ), + ] + ); + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.serverInfoNotLoaded, textAlign: TextAlign.center, style: TextStyle( fontSize: 22, color: Theme.of(context).colorScheme.onSurfaceVariant, ), - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - return ListView( - children: [ - CustomListTile( - title: AppLocalizations.of(context)!.dnsAddresses, - subtitle: AppLocalizations.of(context)!.seeDnsAddresses, - onTap: () { - showModal( - context: context, - builder: (context) => DnsAddressesModal( - dnsAddresses: serverInfo.data!.dnsAddresses - ) - ); - }, + ) + ], ), - CustomListTile( - title: AppLocalizations.of(context)!.dnsPort, - subtitle: serverInfo.data!.dnsPort.toString(), - ), - CustomListTile( - title: AppLocalizations.of(context)!.httpPort, - subtitle: serverInfo.data!.httpPort.toString(), - ), - CustomListTile( - title: AppLocalizations.of(context)!.protectionEnabled, - subtitle: serverInfo.data!.protectionEnabled == true - ? AppLocalizations.of(context)!.yes - : AppLocalizations.of(context)!.no, - ), - CustomListTile( - title: AppLocalizations.of(context)!.dhcpAvailable, - subtitle: serverInfo.data!.dhcpAvailable == true - ? AppLocalizations.of(context)!.yes - : AppLocalizations.of(context)!.no, - ), - CustomListTile( - title: AppLocalizations.of(context)!.serverRunning, - subtitle: serverInfo.data!.running == true - ? AppLocalizations.of(context)!.yes - : AppLocalizations.of(context)!.no, - ), - CustomListTile( - title: AppLocalizations.of(context)!.serverVersion, - subtitle: serverInfo.data!.version, - ), - CustomListTile( - title: AppLocalizations.of(context)!.serverLanguage, - subtitle: serverInfo.data!.language, - ), - ] - ); - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.serverInfoNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - }, + ); + + default: + return const SizedBox(); + } + }, + ), ) ); } diff --git a/lib/screens/settings/theme_modal.dart b/lib/screens/settings/theme_modal.dart index 4d8f2fe..fac867e 100644 --- a/lib/screens/settings/theme_modal.dart +++ b/lib/screens/settings/theme_modal.dart @@ -14,10 +14,10 @@ class ThemeModal extends StatefulWidget { final int selectedTheme; const ThemeModal({ - Key? key, + super.key, required this.statusBarHeight, required this.selectedTheme, - }) : super(key: key); + }); @override State createState() => _ThemeModalState(); diff --git a/lib/screens/settings/update_server/update.dart b/lib/screens/settings/update_server/update.dart index dd91f6e..d1f8d95 100644 --- a/lib/screens/settings/update_server/update.dart +++ b/lib/screens/settings/update_server/update.dart @@ -17,7 +17,7 @@ import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; class UpdateScreen extends StatelessWidget { - const UpdateScreen({Key? key}) : super(key: key); + const UpdateScreen({super.key}); @override Widget build(BuildContext context) { @@ -166,33 +166,38 @@ class UpdateScreen extends StatelessWidget { ); } - final changelog = serversProvider.updateAvailable.loadStatus == LoadStatus.loaded && serversProvider.updateAvailable.data!.changelog != null - ? ListView( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - "Changelog ${serversProvider.updateAvailable.data!.canAutoupdate == true - ? serversProvider.updateAvailable.data!.newVersion - : serversProvider.updateAvailable.data!.currentVersion}", - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurfaceVariant + final SafeArea? changelog; + if (serversProvider.updateAvailable.loadStatus == LoadStatus.loaded && serversProvider.updateAvailable.data!.changelog != null) { + changelog = SafeArea( + child: ListView( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + "Changelog ${serversProvider.updateAvailable.data!.canAutoupdate == true + ? serversProvider.updateAvailable.data!.newVersion + : serversProvider.updateAvailable.data!.currentVersion}", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), ), ), - ), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Html( - data: html.parse(md.markdownToHtml(serversProvider.updateAvailable.data!.changelog!)).outerHtml, - onLinkTap: (url, context, attributes) => url != null ? openUrl(url) : null, + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Html( + data: html.parse(md.markdownToHtml(serversProvider.updateAvailable.data!.changelog!)).outerHtml, + onLinkTap: (url, context, attributes) => url != null ? openUrl(url) : null, + ) ) - ) - ], - ) - : null; + ], + ), + ); + } else { + changelog = null; + } return Scaffold( body: Column( diff --git a/lib/widgets/add_server/add_server_modal.dart b/lib/widgets/add_server/add_server_modal.dart index 59e8a55..e30d1e0 100644 --- a/lib/widgets/add_server/add_server_modal.dart +++ b/lib/widgets/add_server/add_server_modal.dart @@ -550,8 +550,10 @@ class _AddServerModalState extends State { const SizedBox(width: 8) ], ), - body: ListView( - children: form() + body: SafeArea( + child: ListView( + children: form() + ), ), ), ); diff --git a/lib/widgets/system_ui_overlay_style.dart b/lib/widgets/system_ui_overlay_style.dart index 83557f1..ab5b8d0 100644 --- a/lib/widgets/system_ui_overlay_style.dart +++ b/lib/widgets/system_ui_overlay_style.dart @@ -5,12 +5,14 @@ class OverlayStyle extends StatelessWidget { final Widget child; const OverlayStyle({ - Key? key, + super.key, required this.child - }) : super(key: key); + }); @override Widget build(BuildContext context) { + final systemGestureInsets = MediaQuery.of(context).systemGestureInsets; + return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarColor: Colors.transparent, @@ -20,7 +22,13 @@ class OverlayStyle extends StatelessWidget { statusBarIconBrightness: Theme.of(context).brightness == Brightness.light ? Brightness.dark : Brightness.light, - systemNavigationBarColor: Theme.of(context).colorScheme.background, + systemNavigationBarColor: systemGestureInsets.left > 0 // If true gestures navigation + ? Colors.transparent + : ElevationOverlay.applySurfaceTint( + Theme.of(context).colorScheme.surface, + Theme.of(context).colorScheme.surfaceTint, + 3 + ), systemNavigationBarIconBrightness: Theme.of(context).brightness == Brightness.light ? Brightness.dark : Brightness.light, diff --git a/lib/widgets/tab_content_list.dart b/lib/widgets/tab_content_list.dart index d1f2ad0..92b7913 100644 --- a/lib/widgets/tab_content_list.dart +++ b/lib/widgets/tab_content_list.dart @@ -19,7 +19,7 @@ class CustomTabContentList extends StatelessWidget { final EdgeInsets? listPadding; const CustomTabContentList({ - Key? key, + super.key, required this.loadingGenerator, required this.itemsCount, required this.contentWidget, @@ -32,7 +32,7 @@ class CustomTabContentList extends StatelessWidget { this.fabVisible, this.noSliver, this.listPadding - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -49,7 +49,6 @@ class CustomTabContentList extends StatelessWidget { else { return SafeArea( top: false, - bottom: false, child: Builder( builder: (BuildContext context) => CustomScrollView( slivers: [ @@ -72,41 +71,45 @@ class CustomTabContentList extends StatelessWidget { case LoadStatus.loaded: if (noSliver == true) { if (itemsCount > 0) { - return Stack( - children: [ - ListView.builder( - padding: listPadding, - itemCount: itemsCount, - itemBuilder: (context, index) => contentWidget(index), - ), - if (fab != null) AnimatedPositioned( - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - bottom: fabVisible != null && fabVisible == true ? - appConfigProvider.showingSnackbar - ? 70 : 20 - : -70, - right: 20, - child: fab! - ), - ], + return SafeArea( + child: Stack( + children: [ + ListView.builder( + padding: listPadding, + itemCount: itemsCount, + itemBuilder: (context, index) => contentWidget(index), + ), + if (fab != null) AnimatedPositioned( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + bottom: fabVisible != null && fabVisible == true ? + appConfigProvider.showingSnackbar + ? 70 : 20 + : -70, + right: 20, + child: fab! + ), + ], + ), ); } else { - return Stack( - children: [ - noData, - if (fab != null) AnimatedPositioned( - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - bottom: fabVisible != null && fabVisible == true ? - appConfigProvider.showingSnackbar - ? 70 : 20 - : -70, - right: 20, - child: fab! - ), - ], + return SafeArea( + child: Stack( + children: [ + noData, + if (fab != null) AnimatedPositioned( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + bottom: fabVisible != null && fabVisible == true ? + appConfigProvider.showingSnackbar + ? 70 : 20 + : -70, + right: 20, + child: fab! + ), + ], + ), ); } } @@ -146,10 +149,10 @@ class CustomTabContentList extends StatelessWidget { curve: Curves.easeInOut, bottom: fabVisible != null && fabVisible == true ? appConfigProvider.showingSnackbar - ? 70 : 20 - : -70, + ? 90 : 20 + : -90, right: 20, - child: fab! + child: SafeArea(child: fab!) ), ], ); @@ -169,7 +172,6 @@ class CustomTabContentList extends StatelessWidget { else { return SafeArea( top: false, - bottom: false, child: Builder( builder: (BuildContext context) => CustomScrollView( slivers: [ From 33611cb89594e6405c52ece0f0165fa1e1ecc834 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 9 Dec 2023 04:06:35 +0100 Subject: [PATCH 146/177] Improvements --- lib/screens/settings/encryption/encryption.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/screens/settings/encryption/encryption.dart b/lib/screens/settings/encryption/encryption.dart index e11047e..28c1d95 100644 --- a/lib/screens/settings/encryption/encryption.dart +++ b/lib/screens/settings/encryption/encryption.dart @@ -82,10 +82,10 @@ class _EncryptionSettingsState extends State { final result = await Provider.of(context, listen: false).apiClient2!.getEncryptionSettings(); if (!mounted) return; - - final data = result.content as EncryptionData; if (result.successful == true) { + final data = result.content as EncryptionData; + await checkValidDataApi(data: data.toJson()); if (!mounted) return; From 5a12c4c11186a1c99b832d4e675b9e3dabef378e Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 9 Dec 2023 04:07:47 +0100 Subject: [PATCH 147/177] Updated app version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 32d01fc..1ad22e1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.12.3+114 +version: 2.12.4+115 environment: sdk: '>=2.18.1 <3.0.0' From c1e2a796af498233b8a09d19c6939be76dd5ac2d Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 11 Dec 2023 13:33:01 +0100 Subject: [PATCH 148/177] Add refresh button desktop --- lib/screens/home/appbar.dart | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/screens/home/appbar.dart b/lib/screens/home/appbar.dart index 365e606..52ff350 100644 --- a/lib/screens/home/appbar.dart +++ b/lib/screens/home/appbar.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -16,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) { @@ -118,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( From 3aa36c89aaa4e1ffb57e1521a62d2e2b11c7bd51 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 11 Dec 2023 13:54:47 +0100 Subject: [PATCH 149/177] Added reset encryption settings --- lib/l10n/app_en.arb | 7 +- lib/l10n/app_es.arb | 7 +- .../settings/encryption/encryption.dart | 78 ++++++++++++++++--- .../encryption/reset_settings_modal.dart | 52 +++++++++++++ 4 files changed, 130 insertions(+), 14 deletions(-) create mode 100644 lib/screens/settings/encryption/reset_settings_modal.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index fb875cc..11f04c3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -690,5 +690,10 @@ "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." + "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" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index cf328dc..94f4299 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -690,5 +690,10 @@ "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." + "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" } \ No newline at end of file diff --git a/lib/screens/settings/encryption/encryption.dart b/lib/screens/settings/encryption/encryption.dart index 28c1d95..1efdad4 100644 --- a/lib/screens/settings/encryption/encryption.dart +++ b/lib/screens/settings/encryption/encryption.dart @@ -6,8 +6,8 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; -import 'package:adguard_home_manager/screens/settings/encryption/config_error_modal.dart'; import 'package:adguard_home_manager/screens/settings/encryption/status.dart'; +import 'package:adguard_home_manager/screens/settings/encryption/reset_settings_modal.dart'; import 'package:adguard_home_manager/screens/settings/encryption/custom_text_field.dart'; import 'package:adguard_home_manager/screens/settings/encryption/master_switch.dart'; import 'package:adguard_home_manager/screens/settings/encryption/encryption_functions.dart'; @@ -254,6 +254,45 @@ class _EncryptionSettingsState extends State { } } + void resetSettings() async { + ProcessModal processModal = ProcessModal(); + processModal.open(AppLocalizations.of(context)!.resettingConfig); + + final result = await serversProvider.apiClient2!.saveEncryptionSettings( + data: { + "enabled": false, + "server_name": "", + "force_https": false, + "port_https": 443, + "port_dns_over_tls": 853, + "port_dns_over_quic": 853, + "certificate_chain": "", + "private_key": "", + "private_key_saved": false, + "certificate_path": "", + "private_key_path": "", + } + ); + if (!mounted) return; + + processModal.close(); + + if (result.successful == true) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.configurationResetSuccessfully, + color: Colors.green + ); + } + else { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.configurationResetError, + color: Colors.red + ); + } + } + return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.encryptionSettings), @@ -261,17 +300,12 @@ class _EncryptionSettingsState extends State { centerTitle: false, actions: [ IconButton( - onPressed: certKeyValidApi == 2 && (validDataError != null || encryptionResultMessage != null) - ? () => { - showDialog( - context: context, - builder: (context) => EncryptionErrorModal( - error: validDataError ?? encryptionResultMessage ?? AppLocalizations.of(context)!.unknownError - ) - ) - } : null, - icon: generateStatus(context, appConfigProvider, localValidationValid, certKeyValidApi, formEdited), - tooltip: generateStatusString(context, localValidationValid, certKeyValidApi) + onPressed: () => showDialog( + context: context, + builder: (ctx) => ResetSettingsModal(onConfirm: resetSettings) + ), + icon: const Icon(Icons.restore_rounded), + tooltip: AppLocalizations.of(context)!.resetSettings, ), IconButton( onPressed: localValidationValid ? @@ -311,6 +345,26 @@ class _EncryptionSettingsState extends State { case LoadStatus.loaded: return ListView( children: [ + if (certKeyValidApi == 2 && (validDataError != null || encryptionResultMessage != null)) Card( + margin: const EdgeInsets.all(16), + color: Colors.red.withOpacity(0.2), + elevation: 0, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Icon( + Icons.error_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 16), + Expanded( + child: Text(validDataError ?? encryptionResultMessage ?? AppLocalizations.of(context)!.unknownError) + ) + ], + ), + ), + ), EncryptionMasterSwitch( value: enabled, onChange: (value) { diff --git a/lib/screens/settings/encryption/reset_settings_modal.dart b/lib/screens/settings/encryption/reset_settings_modal.dart new file mode 100644 index 0000000..4429162 --- /dev/null +++ b/lib/screens/settings/encryption/reset_settings_modal.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class ResetSettingsModal extends StatelessWidget { + final void Function() onConfirm; + + const ResetSettingsModal({ + super.key, + required this.onConfirm, + }); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Column( + children: [ + Icon( + Icons.restore_rounded, + size: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.resetSettings, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + content: Text( + AppLocalizations.of(context)!.resetEncryptionSettingsDescription, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ), + TextButton( + onPressed: () { + onConfirm(); + Navigator.pop(context); + }, + child: Text(AppLocalizations.of(context)!.confirm) + ), + ], + ); + } +} \ No newline at end of file From c3f55e9ce9c6a9d586b1aaf9916a44986bbd6873 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 11 Dec 2023 14:29:08 +0100 Subject: [PATCH 150/177] Added test upstream dns servers --- lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/screens/settings/dns/dns.dart | 9 + .../settings/dns/test_upstream_dns_modal.dart | 168 ++++++++++++++++++ lib/screens/settings/dns/upstream_dns.dart | 2 +- lib/services/api_client.dart | 14 ++ 6 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 lib/screens/settings/dns/test_upstream_dns_modal.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 11f04c3..1e9271d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -695,5 +695,7 @@ "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" + "configurationResetError": "The configuration couldn't be resetted", + "testUpstreamDnsServers": "Test upstream DNS servers", + "errorTestUpstreamDns": "Error when testing upstream DNS servers." } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 94f4299..f649320 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -695,5 +695,7 @@ "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" + "configurationResetError": "La configuración no ha podido ser reseteada", + "testUpstreamDnsServers": "Probar servidores DNS de subida", + "errorTestUpstreamDns": "Error al probar los servidores DNS de subida." } \ No newline at end of file diff --git a/lib/screens/settings/dns/dns.dart b/lib/screens/settings/dns/dns.dart index 2c711d6..c22572d 100644 --- a/lib/screens/settings/dns/dns.dart +++ b/lib/screens/settings/dns/dns.dart @@ -5,6 +5,7 @@ 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/settings/dns/test_upstream_dns_modal.dart'; import 'package:adguard_home_manager/screens/settings/dns/clear_dns_cache_dialog.dart'; import 'package:adguard_home_manager/screens/settings/dns/cache_config.dart'; import 'package:adguard_home_manager/screens/settings/dns/dns_server_settings.dart'; @@ -84,6 +85,14 @@ class _DnsSettingsState extends State { title: Text(AppLocalizations.of(context)!.dnsSettings), surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ + IconButton( + onPressed: () => showDialog( + context: context, + builder: (ctx) => const TestUpstreamDnsModal() + ), + icon: const Icon(Icons.upload_rounded), + tooltip: AppLocalizations.of(context)!.testUpstreamDnsServers, + ), PopupMenuButton( itemBuilder: (context) => [ PopupMenuItem( diff --git a/lib/screens/settings/dns/test_upstream_dns_modal.dart b/lib/screens/settings/dns/test_upstream_dns_modal.dart new file mode 100644 index 0000000..8410334 --- /dev/null +++ b/lib/screens/settings/dns/test_upstream_dns_modal.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/providers/dns_provider.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; + +class _Item { + final String url; + final bool value; + + const _Item({ + required this.url, + required this.value + }); +} + +class TestUpstreamDnsModal extends StatefulWidget { + const TestUpstreamDnsModal({super.key}); + + @override + State createState() => _TestUpstreamDnsModalState(); +} + +class _TestUpstreamDnsModalState extends State { + LoadStatus loadStatus = LoadStatus.loading; + List<_Item>? values; + + void checkDns() async { + final dnsProvider = Provider.of(context, listen: false); + final result = await Provider.of(context, listen: false).apiClient2!.testUpstreamDns( + body: { + "bootstrap_dns": dnsProvider.dnsInfo!.bootstrapDns, + "fallback_dns": [], + "private_upstream": dnsProvider.dnsInfo!.defaultLocalPtrUpstreams, + "upstream_dns": dnsProvider.dnsInfo!.upstreamDns + } + ); + if (!mounted) return; + if (result.successful == true) { + setState(() { + values = List<_Item>.from( + (result.content as Map).entries.map((e) => _Item( + url: e.key, + value: e.value == "OK" ? true : false + )) + ); + loadStatus = LoadStatus.loaded; + }); + } + else { + setState(() => loadStatus = LoadStatus.error); + } + } + + @override + void initState() { + checkDns(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Column( + children: [ + Icon( + Icons.upload_rounded, + size: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.testUpstreamDnsServers, + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + content: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 500 + ), + child: Builder( + builder: (context) { + switch (loadStatus) { + case LoadStatus.loading: + return const Padding( + padding: EdgeInsets.symmetric(vertical: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CircularProgressIndicator() + ], + ), + ); + + case LoadStatus.loaded: + return SingleChildScrollView( + child: Wrap( + children: values!.map((v) => Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + v.url, + overflow: TextOverflow.ellipsis, + ), + ), + ...[ + const SizedBox(width: 8), + if (v.value == true) const Icon( + Icons.check_circle_rounded, + color: Colors.green, + size: 16, + ), + if (v.value == false) const Icon( + Icons.cancel_rounded, + color: Colors.red, + ) + ] + ], + ), + )).toList(), + ), + ); + + case LoadStatus.error: + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.error_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, + size: 30, + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.errorTestUpstreamDns, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ) + ], + ); + + default: + return const SizedBox(); + } + }, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.close) + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/dns/upstream_dns.dart b/lib/screens/settings/dns/upstream_dns.dart index f952110..e540053 100644 --- a/lib/screens/settings/dns/upstream_dns.dart +++ b/lib/screens/settings/dns/upstream_dns.dart @@ -186,7 +186,7 @@ class _UpstreamDnsScreenState extends State { icon: const Icon(Icons.save_rounded), tooltip: AppLocalizations.of(context)!.save, ), - const SizedBox(width: 10) + const SizedBox(width: 8) ], ), body: SafeArea( diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index f626f75..c2e1fca 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -853,4 +853,18 @@ class ApiClientV2 { ); return ApiResponse(successful: result.successful); } + + Future testUpstreamDns({ + required Map body + }) async { + final result = await HttpRequestClient.post( + urlPath: '/test_upstream_dns', + server: server, + body: body + ); + return ApiResponse( + successful: result.successful, + content: result.body != null ? jsonDecode(result.body!) : null + ); + } } \ No newline at end of file From 6973eae0dea7608bd849f1a92dfdaaf2090eefdf Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 11 Dec 2023 15:48:01 +0100 Subject: [PATCH 151/177] Added custom ip edns field --- lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/models/dns_info.dart | 8 ++ .../settings/dns/dns_server_settings.dart | 96 ++++++++++++++++++- 4 files changed, 108 insertions(+), 4 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1e9271d..15da154 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -697,5 +697,7 @@ "configurationResetSuccessfully": "Configuration resetted successfully", "configurationResetError": "The configuration couldn't be resetted", "testUpstreamDnsServers": "Test upstream DNS servers", - "errorTestUpstreamDns": "Error when testing upstream DNS servers." + "errorTestUpstreamDns": "Error when testing upstream DNS servers.", + "useCustomIpEdns": "Use custom IP for EDNS", + "useCustomIpEdnsDescription": "Allow to use custom IP for EDNS" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index f649320..c226a91 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -697,5 +697,7 @@ "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." + "errorTestUpstreamDns": "Error al probar los servidores DNS de subida.", + "useCustomIpEdns": "Usar IP personalizada para EDNS", + "useCustomIpEdnsDescription": "Permitir usar IP personalizada para EDNS" } \ No newline at end of file diff --git a/lib/models/dns_info.dart b/lib/models/dns_info.dart index 4e85400..0d3aa83 100644 --- a/lib/models/dns_info.dart +++ b/lib/models/dns_info.dart @@ -6,6 +6,8 @@ class DnsInfo { int ratelimit; String blockingMode; bool ednsCsEnabled; + bool? ednsCsUseCustom; + String? ednsCsCustomIp; bool dnssecEnabled; bool disableIpv6; String? upstreamMode; @@ -28,6 +30,8 @@ class DnsInfo { required this.ratelimit, required this.blockingMode, required this.ednsCsEnabled, + required this.ednsCsUseCustom, + required this.ednsCsCustomIp, required this.dnssecEnabled, required this.disableIpv6, required this.upstreamMode, @@ -51,6 +55,8 @@ class DnsInfo { 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"], @@ -74,6 +80,8 @@ class DnsInfo { "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, diff --git a/lib/screens/settings/dns/dns_server_settings.dart b/lib/screens/settings/dns/dns_server_settings.dart index 4cc650a..19f6c09 100644 --- a/lib/screens/settings/dns/dns_server_settings.dart +++ b/lib/screens/settings/dns/dns_server_settings.dart @@ -1,5 +1,6 @@ // ignore_for_file: use_build_context_synchronously +import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -24,7 +25,12 @@ class DnsServerSettingsScreen extends StatefulWidget { class _DnsServerSettingsScreenState extends State { final TextEditingController limitRequestsController = TextEditingController(); String? limitRequestsError; + final _expandableCustomEdns = ExpandableController(); + final _expandableEdnsIp = ExpandableController(); bool enableEdns = false; + bool useCustomIpEdns = false; + final _customIpEdnsController = TextEditingController(); + String? ednsIpError; bool enableDnssec = false; bool disableIpv6Resolving = false; @@ -48,6 +54,17 @@ class _DnsServerSettingsScreenState extends State { validateData(); } + void validateEdns(String value) { + RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$'); + if (ipAddress.hasMatch(value) == true) { + setState(() => ednsIpError = null); + } + else { + setState(() => ednsIpError = AppLocalizations.of(context)!.ipNotValid); + } + validateData(); + } + void validateIpv6(String value) { RegExp ipAddress = RegExp(r'(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$)'); if (ipAddress.hasMatch(value) == true) { @@ -72,7 +89,8 @@ class _DnsServerSettingsScreenState extends State { ipv6controller.text != '' && ipv6error == null ) - ) == true + ) == true && + ednsIpError == null ) { setState(() => isDataValid = true); } @@ -87,6 +105,10 @@ class _DnsServerSettingsScreenState extends State { limitRequestsController.text = dnsProvider.dnsInfo!.ratelimit.toString(); enableEdns = dnsProvider.dnsInfo!.ednsCsEnabled; + useCustomIpEdns = dnsProvider.dnsInfo!.ednsCsUseCustom ?? false; + _customIpEdnsController.text = dnsProvider.dnsInfo!.ednsCsCustomIp ?? ""; + if (dnsProvider.dnsInfo!.ednsCsEnabled == true) _expandableCustomEdns.toggle(); + if (dnsProvider.dnsInfo!.ednsCsUseCustom == true) _expandableEdnsIp.toggle(); enableDnssec = dnsProvider.dnsInfo!.dnssecEnabled; disableIpv6Resolving = dnsProvider.dnsInfo!.disableIpv6; blockingMode = dnsProvider.dnsInfo!.blockingMode; @@ -109,6 +131,8 @@ class _DnsServerSettingsScreenState extends State { final result = await dnsProvider.saveDnsServerConfig({ "ratelimit": int.parse(limitRequestsController.text), "edns_cs_enabled": enableEdns, + "edns_cs_use_custom": useCustomIpEdns, + "edns_cs_custom_ip": _customIpEdnsController.text, "dnssec_enabled": enableDnssec, "disable_ipv6": disableIpv6Resolving, "blocking_mode": blockingMode, @@ -200,10 +224,78 @@ class _DnsServerSettingsScreenState extends State { const SizedBox(height: 10), CustomSwitchListTile( value: enableEdns, - onChanged: (value) => setState(() => enableEdns = value), + onChanged: (value) => setState(() { + enableEdns = value; + _expandableCustomEdns.toggle(); + if (value == false) { + useCustomIpEdns = false; + if (_expandableEdnsIp.expanded == true) _expandableEdnsIp.toggle(); + _customIpEdnsController.text = ""; + ednsIpError = null; + } + validateData(); + }), title: AppLocalizations.of(context)!.enableEdns, subtitle: AppLocalizations.of(context)!.enableEdnsDescription, ), + ExpandableNotifier( + controller: _expandableCustomEdns, + child: Expandable( + collapsed: const SizedBox(), + expanded: Column( + children: [ + CustomSwitchListTile( + padding: const EdgeInsets.only( + left: 50, + top: 12, + bottom: 12, + right: 16 + ), + value: useCustomIpEdns, + onChanged: (value) => setState(() { + useCustomIpEdns = value; + _expandableEdnsIp.toggle(); + if (useCustomIpEdns == false) { + _customIpEdnsController.text = ""; + ednsIpError = null; + } + validateData(); + }), + title: AppLocalizations.of(context)!.useCustomIpEdns, + subtitle: AppLocalizations.of(context)!.useCustomIpEdnsDescription, + ), + ExpandableNotifier( + controller: _expandableEdnsIp, + child: Expandable( + collapsed: const SizedBox(), + expanded: Padding( + padding: const EdgeInsets.only( + top: 16, + bottom: 16, + right: 16, + left: 70 + ), + child: TextFormField( + controller: _customIpEdnsController, + onChanged: validateEdns, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.link_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ednsIpError, + labelText: AppLocalizations.of(context)!.ipAddress, + ), + ), + ), + ) + ), + ], + ), + ) + ), CustomSwitchListTile( value: enableDnssec, onChanged: (value) => setState(() => enableDnssec = value), From c3530f17abfd403cd4a63f194672f25972337478 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 11 Dec 2023 18:32:28 +0100 Subject: [PATCH 152/177] Sort top items --- lib/l10n/app_en.arb | 5 +- lib/l10n/app_es.arb | 5 +- .../home/top_items/top_items_screen.dart | 58 ++++++++++++++++++- 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 15da154..2ae59bf 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -699,5 +699,8 @@ "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" + "useCustomIpEdnsDescription": "Allow to use custom IP for EDNS", + "sortingOptions": "Sorting options", + "fromHighestToLowest": "From highest to lowest", + "fromLowestToHighest": "From lowest to highest" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index c226a91..1917370 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -699,5 +699,8 @@ "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" + "useCustomIpEdnsDescription": "Permitir usar IP personalizada para EDNS", + "sortingOptions": "Opciones de ordenación", + "fromHighestToLowest": "De mayor a menor", + "fromLowestToHighest": "De menor a mayor" } \ No newline at end of file diff --git a/lib/screens/home/top_items/top_items_screen.dart b/lib/screens/home/top_items/top_items_screen.dart index 7e7e623..f2f9b4d 100644 --- a/lib/screens/home/top_items/top_items_screen.dart +++ b/lib/screens/home/top_items/top_items_screen.dart @@ -15,6 +15,8 @@ import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/functions/number_format.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; +enum _SortingOptions { highestToLowest, lowestToHighest } + class TopItemsScreen extends StatefulWidget { final HomeTopItems type; final String title; @@ -44,6 +46,7 @@ class TopItemsScreen extends StatefulWidget { } class _TopItemsScreenState extends State { + _SortingOptions _sortingOptions = _SortingOptions.highestToLowest; bool searchActive = false; final TextEditingController searchController = TextEditingController(); @@ -68,6 +71,10 @@ class _TopItemsScreenState extends State { for (var element in data) { total = total + double.parse(element.values.toList()[0].toString()); } + + final sortedValues = _sortingOptions == _SortingOptions.lowestToHighest + ? screenData.reversed.toList() + : screenData.toList(); if (widget.isFullscreen == true) { return Dialog.fullscreen( @@ -119,6 +126,53 @@ class _TopItemsScreenState extends State { icon: const Icon(Icons.clear_rounded), tooltip: AppLocalizations.of(context)!.clearSearch, ), + PopupMenuButton( + icon: const Icon(Icons.sort_rounded), + itemBuilder: (context) => [ + PopupMenuItem( + onTap: () => setState(() => _sortingOptions = _SortingOptions.highestToLowest), + child: Row( + children: [ + const Icon(Icons.arrow_downward_rounded), + const SizedBox(width: 8), + Expanded( + child: Text(AppLocalizations.of(context)!.fromHighestToLowest) + ), + const SizedBox(width: 16), + Icon( + _sortingOptions == _SortingOptions.highestToLowest + ? Icons.radio_button_checked_rounded + : Icons.radio_button_unchecked_rounded, + color: _sortingOptions == _SortingOptions.highestToLowest + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurfaceVariant, + ) + ], + ) + ), + PopupMenuItem( + onTap: () => setState(() => _sortingOptions = _SortingOptions.lowestToHighest), + child: Row( + children: [ + const Icon(Icons.arrow_upward_rounded), + const SizedBox(width: 8), + Expanded( + child: Text(AppLocalizations.of(context)!.fromLowestToHighest) + ), + const SizedBox(width: 16), + Icon( + _sortingOptions == _SortingOptions.lowestToHighest + ? Icons.radio_button_checked_rounded + : Icons.radio_button_unchecked_rounded, + color: _sortingOptions == _SortingOptions.lowestToHighest + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurfaceVariant, + ) + ], + ) + ), + ], + ), const SizedBox(width: 8) ], ), @@ -128,7 +182,7 @@ class _TopItemsScreenState extends State { isClient: widget.isClient, onTapEntry: widget.onTapEntry, options: widget.options, - screenData: screenData, + screenData: sortedValues, total: total, withProgressBar: widget.withProgressBar, ), @@ -193,7 +247,7 @@ class _TopItemsScreenState extends State { isClient: widget.isClient, onTapEntry: widget.onTapEntry, options: widget.options, - screenData: screenData, + screenData: sortedValues, total: total, withProgressBar: widget.withProgressBar, ), From cc12a8504eb667fa3b6ff245cc2c096b1d3b1024 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 11 Dec 2023 21:49:47 +0100 Subject: [PATCH 153/177] Added client queries and statistics settings --- lib/l10n/app_en.arb | 6 +++- lib/l10n/app_es.arb | 6 +++- lib/models/clients.dart | 10 +++++- lib/screens/clients/added_list.dart | 6 ++-- lib/screens/clients/client/client_form.dart | 31 +++++++++++++++++++ lib/screens/clients/client/client_screen.dart | 17 +++++++++- 6 files changed, 69 insertions(+), 7 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2ae59bf..1a4cdb4 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -702,5 +702,9 @@ "useCustomIpEdnsDescription": "Allow to use custom IP for EDNS", "sortingOptions": "Sorting options", "fromHighestToLowest": "From highest to lowest", - "fromLowestToHighest": "From lowest to highest" + "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..." } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 1917370..e0e57c1 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -702,5 +702,9 @@ "useCustomIpEdnsDescription": "Permitir usar IP personalizada para EDNS", "sortingOptions": "Opciones de ordenación", "fromHighestToLowest": "De mayor a menor", - "fromLowestToHighest": "De menor a mayor" + "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..." } \ No newline at end of file diff --git a/lib/models/clients.dart b/lib/models/clients.dart index 1472a0f..5808391 100644 --- a/lib/models/clients.dart +++ b/lib/models/clients.dart @@ -87,6 +87,8 @@ class Client { final bool useGlobalBlockedServices; final bool useGlobalSettings; final SafeSearch? safeSearch; + final bool? ignoreQuerylog; + final bool? ignoreStatistics; Client({ required this.name, @@ -100,6 +102,8 @@ class Client { required this.useGlobalBlockedServices, required this.useGlobalSettings, required this.safeSearch, + required this.ignoreQuerylog, + required this.ignoreStatistics, }); factory Client.fromJson(Map json) => Client( @@ -115,7 +119,9 @@ class Client { 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"] ); Map toJson() => { @@ -130,5 +136,7 @@ class Client { "safe_search": safeSearch, "use_global_blocked_services": useGlobalBlockedServices, "use_global_settings": useGlobalSettings, + "ignore_querylog": ignoreQuerylog, + "ignore_statistics": ignoreStatistics, }; } \ No newline at end of file diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index 0a0bc08..aa86785 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -29,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 createState() => _AddedListState(); @@ -75,7 +75,7 @@ class _AddedListState extends State { void confirmEditClient(Client client) async { ProcessModal processModal = ProcessModal(); - processModal.open(AppLocalizations.of(context)!.addingClient); + processModal.open(AppLocalizations.of(context)!.savingChanges); final result = await clientsProvider.editClient(client); diff --git a/lib/screens/clients/client/client_form.dart b/lib/screens/clients/client/client_form.dart index 2151288..5c164af 100644 --- a/lib/screens/clients/client/client_form.dart +++ b/lib/screens/clients/client/client_form.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -45,6 +46,10 @@ class ClientForm extends StatelessWidget { 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; const ClientForm({ super.key, @@ -75,6 +80,10 @@ class ClientForm extends StatelessWidget { required this.updateEnableSafeSearch, required this.updateSafeSearch, required this.updateUseGlobalSettingsServices, + required this.ignoreClientQueryLog, + required this.ignoreClientStatistics, + required this.updateIgnoreClientQueryLog, + required this.updateIgnoreClientStatistics, }); @override @@ -217,6 +226,28 @@ class ClientForm extends StatelessWidget { ) : null, ), + SectionLabel( + label: AppLocalizations.of(context)!.queryLogsAndStatistics, + padding: const EdgeInsets.all(24), + ), + CustomSwitchListTile( + title: AppLocalizations.of(context)!.ignoreClientQueryLog, + value: ignoreClientQueryLog, + onChanged: updateIgnoreClientQueryLog, + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 4 + ), + ), + CustomSwitchListTile( + title: AppLocalizations.of(context)!.ignoreClientStatistics, + value: ignoreClientStatistics, + onChanged: updateIgnoreClientStatistics, + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 4 + ), + ), SectionLabel( label: AppLocalizations.of(context)!.blockedServices, padding: const EdgeInsets.all(24), diff --git a/lib/screens/clients/client/client_screen.dart b/lib/screens/clients/client/client_screen.dart index d4628e7..9a16e1a 100644 --- a/lib/screens/clients/client/client_screen.dart +++ b/lib/screens/clients/client/client_screen.dart @@ -73,6 +73,9 @@ class _ClientScreenState extends State { List upstreamServers = []; + bool _ignoreClientQueryLog = false; + bool _ignoreClientStatistics = false; + void enableDisableGlobalSettingsFiltering() { if (useGlobalSettingsFiltering == true) { setState(() { @@ -120,6 +123,8 @@ class _ClientScreenState extends State { id: uuid.v4(), controller: TextEditingController(text: e) )).toList(); + _ignoreClientQueryLog = widget.client!.ignoreQuerylog ?? false; + _ignoreClientStatistics = widget.client!.ignoreStatistics ?? false; } super.initState(); } @@ -140,7 +145,9 @@ class _ClientScreenState extends State { useGlobalBlockedServices: useGlobalSettingsServices, blockedServices: blockedServices, upstreams: List.from(upstreamServers.map((e) => e.controller.text)), - tags: selectedTags + tags: selectedTags, + ignoreQuerylog: _ignoreClientQueryLog, + ignoreStatistics: _ignoreClientStatistics ); widget.onConfirm(client); } @@ -214,6 +221,10 @@ class _ClientScreenState extends State { 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), ), ), ), @@ -281,6 +292,10 @@ class _ClientScreenState extends State { 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), ), ) ], From ddf9683e88c56cfba2f171ffafd2775173d43208 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 11 Dec 2023 21:49:52 +0100 Subject: [PATCH 154/177] Updated libraries --- pubspec.lock | 42 +++++++++++++++++++++--------------------- pubspec.yaml | 10 +++++----- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 274cfb3..963ce6f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: "direct main" description: name: animations - sha256: ef57563eed3620bd5d75ad96189846aca1e033c0c45fc9a7d26e80ab02b88a70 + sha256: "708e4b68c23228c264b038fe7003a2f5d01ce85fc64d8cae090e86b27fcea6c5" url: "https://pub.dev" source: hosted - version: "2.0.8" + version: "2.0.10" ansicolor: dependency: transitive description: @@ -271,10 +271,10 @@ packages: dependency: "direct dev" description: name: flutter_native_splash - sha256: c4d899312b36e7454bedfd0a4740275837b99e532d81c8477579d8183db1de6c + sha256: "141b20f15a2c4fe6e33c49257ca1bc114fc5c500b04fcbc8d75016bb86af672f" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.8" flutter_reorderable_list: dependency: "direct main" description: @@ -330,10 +330,10 @@ packages: dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" http_parser: dependency: transitive description: @@ -474,10 +474,10 @@ packages: dependency: transitive description: name: petitparser - sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "6.0.2" pie_chart: dependency: "direct main" description: @@ -575,18 +575,18 @@ packages: dependency: "direct main" description: name: sqflite_common_ffi - sha256: "35d2fce1e971707c227cc4775cc017d5eafe06c2654c3435ebd5c3ad6c170f5f" + sha256: "873677ee78738a723d1ded4ccb23980581998d873d30ee9c331f6a81748663ff" url: "https://pub.dev" source: hosted - version: "2.3.0+4" + version: "2.3.1" sqlite3: dependency: transitive description: name: sqlite3 - sha256: db65233e6b99e99b2548932f55a987961bc06d82a31a0665451fa0b4fff4c3fb + sha256: "8922805564b78eb7aa9386c10056d377a541ac7270dc6a1589176277ebb4d15d" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" sqlite3_flutter_libs: dependency: "direct main" description: @@ -671,10 +671,10 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba + sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86 url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.2" url_launcher_android: dependency: transitive description: @@ -719,10 +719,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" + sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.2" url_launcher_windows: dependency: transitive description: @@ -783,10 +783,10 @@ packages: dependency: transitive description: name: win32 - sha256: "7c99c0e1e2fa190b48d25c81ca5e42036d5cac81430ef249027d97b0935c553f" + sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.1.1" win32_registry: dependency: transitive description: @@ -808,10 +808,10 @@ packages: dependency: transitive description: name: xml - sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.4.2" + version: "6.5.0" yaml: dependency: transitive description: @@ -822,4 +822,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.2.0 <4.0.0" - flutter: ">=3.13.0" + flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index 1ad22e1..4f19f1c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,7 +44,7 @@ dependencies: package_info_plus: ^5.0.1 flutter_displaymode: ^0.6.0 dynamic_color: ^1.6.8 - animations: ^2.0.8 + animations: ^2.0.10 device_info_plus: ^9.1.1 uuid: ^4.2.1 expandable: ^5.0.1 @@ -58,7 +58,7 @@ dependencies: html: ^0.15.4 flutter_html: ^3.0.0-beta.2 sqlite3_flutter_libs: ^0.5.18 - sqflite_common_ffi: ^2.3.0+4 + sqflite_common_ffi: ^2.3.1 window_size: git: url: https://github.com/google/flutter-desktop-embedding @@ -67,7 +67,7 @@ dependencies: git: url: https://github.com/JGeek00/flutter_split_view ref: master-alt - url_launcher: ^6.1.11 + url_launcher: ^6.2.2 contextmenu: ^3.0.0 async: ^2.11.0 sentry_flutter: ^7.13.2 @@ -75,7 +75,7 @@ dependencies: flutter_reorderable_list: ^1.3.1 pie_chart: ^5.4.0 segmented_button_slide: ^1.0.4 - http: ^1.1.0 + http: ^1.1.2 dev_dependencies: flutter_test: @@ -88,7 +88,7 @@ dev_dependencies: # rules and activating additional ones. flutter_lints: ^3.0.1 flutter_launcher_icons: ^0.13.1 - flutter_native_splash: ^2.3.6 + flutter_native_splash: ^2.3.8 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From 27e0b5152ec065a297646b26c840df9b360eb3f2 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 11 Dec 2023 22:03:36 +0100 Subject: [PATCH 155/177] Added fallback DNS servers --- lib/l10n/app_en.arb | 6 +- lib/l10n/app_es.arb | 6 +- lib/models/dns_info.dart | 4 + lib/providers/dns_provider.dart | 16 ++ lib/screens/settings/dns/dns.dart | 7 + lib/screens/settings/dns/fallback_dns.dart | 224 +++++++++++++++++++++ 6 files changed, 261 insertions(+), 2 deletions(-) create mode 100644 lib/screens/settings/dns/fallback_dns.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1a4cdb4..7fd17e2 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -706,5 +706,9 @@ "queryLogsAndStatistics": "Query logs and statistics", "ignoreClientQueryLog": "Ignore this client in query log", "ignoreClientStatistics": "Ignore this client in statistics", - "savingChanges": "Saving changes..." + "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." } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index e0e57c1..33b69b1 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -706,5 +706,9 @@ "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..." + "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." } \ No newline at end of file diff --git a/lib/models/dns_info.dart b/lib/models/dns_info.dart index 0d3aa83..20338d3 100644 --- a/lib/models/dns_info.dart +++ b/lib/models/dns_info.dart @@ -2,6 +2,7 @@ class DnsInfo { List upstreamDns; String? upstreamDnsFile; List bootstrapDns; + List? fallbackDns; bool protectionEnabled; int ratelimit; String blockingMode; @@ -26,6 +27,7 @@ class DnsInfo { required this.upstreamDns, required this.upstreamDnsFile, required this.bootstrapDns, + required this.fallbackDns, required this.protectionEnabled, required this.ratelimit, required this.blockingMode, @@ -51,6 +53,7 @@ class DnsInfo { upstreamDns: json["upstream_dns"] != null ? List.from(json["upstream_dns"].map((x) => x)) : [], upstreamDnsFile: json["upstream_dns_file"], bootstrapDns: json["bootstrap_dns"] != null ? List.from(json["bootstrap_dns"].map((x) => x)) : [], + fallbackDns: json["fallback_dns"] != null ? List.from(json["fallback_dns"].map((x) => x)) : [], protectionEnabled: json["protection_enabled"], ratelimit: json["ratelimit"], blockingMode: json["blocking_mode"], @@ -76,6 +79,7 @@ class DnsInfo { "upstream_dns": List.from(upstreamDns.map((x) => x)), "upstream_dns_file": upstreamDnsFile, "bootstrap_dns": List.from(bootstrapDns.map((x) => x)), + "fallback_dns": List.from(bootstrapDns.map((x) => x)), "protection_enabled": protectionEnabled, "ratelimit": ratelimit, "blocking_mode": blockingMode, diff --git a/lib/providers/dns_provider.dart b/lib/providers/dns_provider.dart index 0929f3a..5a91dfc 100644 --- a/lib/providers/dns_provider.dart +++ b/lib/providers/dns_provider.dart @@ -112,6 +112,22 @@ class DnsProvider with ChangeNotifier { } } + Future saveFallbackDnsConfig(Map value) async { + final result = await _serversProvider!.apiClient2!.setDnsConfig( + data: value + ); + + if (result.successful == true) { + DnsInfo data = dnsInfo!; + data.bootstrapDns = List.from(value['fallback_dns']); + setDnsInfoData(data); + return result; + } + else { + return result; + } + } + Future saveCacheCacheConfig(Map value) async { final result = await _serversProvider!.apiClient2!.setDnsConfig( data: value diff --git a/lib/screens/settings/dns/dns.dart b/lib/screens/settings/dns/dns.dart index c22572d..4a7c78a 100644 --- a/lib/screens/settings/dns/dns.dart +++ b/lib/screens/settings/dns/dns.dart @@ -5,6 +5,7 @@ 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/settings/dns/fallback_dns.dart'; import 'package:adguard_home_manager/screens/settings/dns/test_upstream_dns_modal.dart'; import 'package:adguard_home_manager/screens/settings/dns/clear_dns_cache_dialog.dart'; import 'package:adguard_home_manager/screens/settings/dns/cache_config.dart'; @@ -167,6 +168,12 @@ class _DnsSettingsState extends State { onTap: () => navigate(const BootstrapDnsScreen()), icon: Icons.dns_rounded, ), + if (dnsProvider.dnsInfo!.fallbackDns != null) CustomListTile( + title: AppLocalizations.of(context)!.fallbackDnsServers, + subtitle: AppLocalizations.of(context)!.fallbackDnsServersDescription, + onTap: () => navigate(const FallbackDnsScreen()), + icon: Icons.alt_route_rounded, + ), CustomListTile( title: AppLocalizations.of(context)!.privateReverseDnsServers, subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription, diff --git a/lib/screens/settings/dns/fallback_dns.dart b/lib/screens/settings/dns/fallback_dns.dart new file mode 100644 index 0000000..4df30c9 --- /dev/null +++ b/lib/screens/settings/dns/fallback_dns.dart @@ -0,0 +1,224 @@ +// 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/functions/desktop_mode.dart'; +import 'package:adguard_home_manager/classes/process_modal.dart'; +import 'package:adguard_home_manager/providers/dns_provider.dart'; +import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; + +class FallbackDnsScreen extends StatefulWidget { + const FallbackDnsScreen({super.key}); + + @override + State createState() => _FallbackDnsScreenState(); +} + +class _FallbackDnsScreenState extends State { + List> fallbackControllers = []; + + bool validValues = false; + + void validateIp(Map field, String value) { + RegExp ipAddress = RegExp(r'(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$)'); + if (ipAddress.hasMatch(value) == true) { + setState(() => field['error'] = null); + } + else { + setState(() => field['error'] = AppLocalizations.of(context)!.invalidIp); + } + checkValidValues(); + } + + void checkValidValues() { + if ( + fallbackControllers.isNotEmpty && + fallbackControllers.every((element) => element['controller'].text != '') && + fallbackControllers.every((element) => element['error'] == null) + ) { + setState(() => validValues = true); + } + else { + setState(() => validValues = false); + } + } + + @override + void initState() { + final dnsProvider = Provider.of(context, listen: false); + + for (var item in dnsProvider.dnsInfo!.fallbackDns!) { + final controller = TextEditingController(); + controller.text = item; + fallbackControllers.add({ + 'controller': controller, + 'error': null + }); + } + validValues = true; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final dnsProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); + + final width = MediaQuery.of(context).size.width; + + void saveData() async { + ProcessModal processModal = ProcessModal(); + processModal.open(AppLocalizations.of(context)!.savingConfig); + + final result = await dnsProvider.saveFallbackDnsConfig({ + "fallback_dns": fallbackControllers.map((e) => e['controller'].text).toList(), + }); + + processModal.close(); + + if (result.successful == true) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.dnsConfigSaved, + color: Colors.green + ); + } + else if (result.successful == false && result.statusCode == 400) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.someValueNotValid, + color: Colors.red + ); + } + else { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.dnsConfigNotSaved, + color: Colors.red + ); + } + } + + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.fallbackDnsServers), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, + actions: [ + IconButton( + onPressed: validValues == true + ? () => saveData() + : null, + icon: const Icon(Icons.save_rounded), + tooltip: AppLocalizations.of(context)!.save, + ), + const SizedBox(width: 10) + ], + ), + body: SafeArea( + child: ListView( + padding: const EdgeInsets.only(top: 10), + children: [ + Card( + margin: const EdgeInsets.only( + left: 16, right: 16, bottom: 20 + ), + child: Padding( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + Icon( + Icons.info_rounded, + color: Theme.of(context).listTileTheme.iconColor, + ), + const SizedBox(width: 20), + Flexible( + child: Text( + AppLocalizations.of(context)!.fallbackDnsServersInfo, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) + ) + ], + ), + ), + ), + const SizedBox(height: 10), + if (fallbackControllers.isEmpty) Column( + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: Center( + child: Text( + AppLocalizations.of(context)!.noFallbackDnsAdded, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 16 + ), + ), + ), + ), + const SizedBox(height: 20), + ], + ), + ...fallbackControllers.map((c) => Padding( + padding: const EdgeInsets.only( + left: 16, right: 6, bottom: 20 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: TextFormField( + controller: c['controller'], + onChanged: (value) => validateIp(c, value), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.dns_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: c['error'], + labelText: AppLocalizations.of(context)!.dnsServer, + ) + ), + ), + const SizedBox(width: 8), + IconButton( + onPressed: () { + setState(() => fallbackControllers = fallbackControllers.where((con) => con != c).toList()); + checkValidValues(); + }, + icon: const Icon(Icons.remove_circle_outline) + ) + ], + ), + )), + Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton.icon( + onPressed: () { + setState(() => fallbackControllers.add({ + 'controller': TextEditingController(), + 'error': null + })); + checkValidValues(); + }, + icon: const Icon(Icons.add), + label: Text(AppLocalizations.of(context)!.addItem) + ), + ], + ), + const SizedBox(height: 20) + ], + ), + ), + ); + } +} \ No newline at end of file From 304c3aba540bf9222313c1147d5da9e19de363cd Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 11 Dec 2023 22:24:24 +0100 Subject: [PATCH 156/177] Added blocked response ttl field --- lib/l10n/app_en.arb | 5 +- lib/l10n/app_es.arb | 5 +- lib/models/dns_info.dart | 4 ++ lib/providers/dns_provider.dart | 1 + .../settings/dns/dns_server_settings.dart | 47 +++++++++++++++++-- 5 files changed, 56 insertions(+), 6 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7fd17e2..4fe20b3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -710,5 +710,8 @@ "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." + "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" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 33b69b1..d3e0855 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -710,5 +710,8 @@ "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." + "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" } \ No newline at end of file diff --git a/lib/models/dns_info.dart b/lib/models/dns_info.dart index 20338d3..c0cabd6 100644 --- a/lib/models/dns_info.dart +++ b/lib/models/dns_info.dart @@ -22,6 +22,7 @@ class DnsInfo { String blockingIpv4; String blockingIpv6; List defaultLocalPtrUpstreams; + int? blockedResponseTtl; DnsInfo({ required this.upstreamDns, @@ -47,6 +48,7 @@ class DnsInfo { required this.blockingIpv4, required this.blockingIpv6, required this.defaultLocalPtrUpstreams, + required this.blockedResponseTtl, }); factory DnsInfo.fromJson(Map json) => DnsInfo( @@ -73,6 +75,7 @@ class DnsInfo { blockingIpv4: json["blocking_ipv4"], blockingIpv6: json["blocking_ipv6"], defaultLocalPtrUpstreams: json["default_local_ptr_upstreams"] != null ? List.from(json["default_local_ptr_upstreams"].map((x) => x)) : [], + blockedResponseTtl: json["blocked_response_ttl"] ); Map toJson() => { @@ -99,5 +102,6 @@ class DnsInfo { "blocking_ipv4": blockingIpv4, "blocking_ipv6": blockingIpv6, "default_local_ptr_upstreams": List.from(defaultLocalPtrUpstreams.map((x) => x)), + "blocked_response_ttl": blockedResponseTtl }; } diff --git a/lib/providers/dns_provider.dart b/lib/providers/dns_provider.dart index 5a91dfc..c5809af 100644 --- a/lib/providers/dns_provider.dart +++ b/lib/providers/dns_provider.dart @@ -161,6 +161,7 @@ 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 result; } diff --git a/lib/screens/settings/dns/dns_server_settings.dart b/lib/screens/settings/dns/dns_server_settings.dart index 19f6c09..ba04422 100644 --- a/lib/screens/settings/dns/dns_server_settings.dart +++ b/lib/screens/settings/dns/dns_server_settings.dart @@ -41,6 +41,9 @@ class _DnsServerSettingsScreenState extends State { final TextEditingController ipv6controller = TextEditingController(); String? ipv6error; + final _ttlController = TextEditingController(); + String? _ttlError; + bool isDataValid = false; void validateIpv4(String value) { @@ -90,7 +93,8 @@ class _DnsServerSettingsScreenState extends State { ipv6error == null ) ) == true && - ednsIpError == null + ednsIpError == null && + _ttlError == null ) { setState(() => isDataValid = true); } @@ -99,6 +103,17 @@ class _DnsServerSettingsScreenState extends State { } } + void validateNumber(String value) { + final regex = RegExp(r'^(\d)+$'); + if (regex.hasMatch(value) == true) { + setState(() => _ttlError = null); + } + else { + setState(() => _ttlError = AppLocalizations.of(context)!.invalidValue); + } + validateData(); + } + @override void initState() { final dnsProvider = Provider.of(context, listen: false); @@ -115,6 +130,9 @@ class _DnsServerSettingsScreenState extends State { ipv4controller.text = dnsProvider.dnsInfo!.blockingIpv4; ipv6controller.text = dnsProvider.dnsInfo!.blockingIpv6; isDataValid = true; + _ttlController.text = dnsProvider.dnsInfo!.blockedResponseTtl != null + ? dnsProvider.dnsInfo!.blockedResponseTtl.toString() + : ""; super.initState(); } @@ -137,7 +155,8 @@ class _DnsServerSettingsScreenState extends State { "disable_ipv6": disableIpv6Resolving, "blocking_mode": blockingMode, "blocking_ipv4": ipv4controller.text, - "blocking_ipv6": ipv6controller.text + "blocking_ipv6": ipv6controller.text, + "blocked_response_ttl": int.parse(_ttlController.text) }); processModal.close(); @@ -392,8 +411,28 @@ class _DnsServerSettingsScreenState extends State { keyboardType: TextInputType.number, ), ), - const SizedBox(height: 30) - ] + const SizedBox(height: 30), + ], + Padding( + padding: const EdgeInsets.all(16), + child: TextFormField( + controller: _ttlController, + onChanged: validateNumber, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.timer_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: _ttlError, + labelText: AppLocalizations.of(context)!.blockedResponseTtl, + helperText: AppLocalizations.of(context)!.blockedResponseTtlDescription, + helperMaxLines: 2, + ), + keyboardType: TextInputType.number, + ), + ), ], ), ), From 28bc01a7b3b53188745625b29d7ebd14bffa75ea Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 11 Dec 2023 22:25:44 +0100 Subject: [PATCH 157/177] Updated app version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 4f19f1c..8672c5a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.12.4+115 +version: 2.13.0-beta.1+116 environment: sdk: '>=2.18.1 <3.0.0' From 0b0f499c4ac42a58629ffeff80710c6185bb7cfc Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Tue, 12 Dec 2023 18:43:38 +0100 Subject: [PATCH 158/177] Updates --- ios/Podfile.lock | 40 ++++++++++++++++++++-------------------- macos/Podfile.lock | 4 ++-- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index ed1a57c..c3ba953 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -11,28 +11,28 @@ PODS: - FMDB/standard (2.7.5) - package_info_plus (0.4.5): - Flutter - - Sentry/HybridSDK (8.9.1): - - SentryPrivate (= 8.9.1) + - Sentry/HybridSDK (8.15.2): + - SentryPrivate (= 8.15.2) - sentry_flutter (0.0.1): - Flutter - FlutterMacOS - - Sentry/HybridSDK (= 8.9.1) - - SentryPrivate (8.9.1) + - Sentry/HybridSDK (= 8.15.2) + - SentryPrivate (8.15.2) - sqflite (0.0.3): - Flutter - FMDB (>= 2.7.5) - - sqlite3 (3.43.1): - - sqlite3/common (= 3.43.1) - - sqlite3/common (3.43.1) - - sqlite3/fts5 (3.43.1): + - sqlite3 (3.44.0): + - sqlite3/common (= 3.44.0) + - sqlite3/common (3.44.0) + - sqlite3/fts5 (3.44.0): - sqlite3/common - - sqlite3/perf-threadsafe (3.43.1): + - sqlite3/perf-threadsafe (3.44.0): - sqlite3/common - - sqlite3/rtree (3.43.1): + - sqlite3/rtree (3.44.0): - sqlite3/common - sqlite3_flutter_libs (0.0.1): - Flutter - - sqlite3 (~> 3.43.0) + - sqlite3 (~> 3.44.0) - sqlite3/fts5 - sqlite3/perf-threadsafe - sqlite3/rtree @@ -83,21 +83,21 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea + device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef flutter_web_browser: 7bccaafbb0c5b8862afe7bcd158f15557109f61f FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 - Sentry: e3203780941722a1fcfee99e351de14244c7f806 - sentry_flutter: 8f0ffd53088e6a4d50c095852c5cad9e4405025c - SentryPrivate: 5e3683390f66611fc7c6215e27645873adb55d13 + package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 + Sentry: 6f5742b4c47c17c9adcf265f6f328cf4a0ed1923 + sentry_flutter: 2c309a1d4b45e59d02cfa15795705687f1e2081b + SentryPrivate: b2f7996f37781080f04a946eb4e377ff63c64195 sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a - sqlite3: e0a0623a33a20a47cb5921552aebc6e9e437dc91 - sqlite3_flutter_libs: 878ccbdcfd7b7cb41a774ec238223d876880c5ec + sqlite3: 6e2d4a4879854d0ec86b476bf3c3e30870bac273 + sqlite3_flutter_libs: eb769059df0356dc52ddda040f09cacc9391a7cf store_checker: 359c5051d9ec30ff0a8fa39eb5ec9df021bb745d - url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 + url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 -COCOAPODS: 1.12.1 +COCOAPODS: 1.14.3 diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 4543f6f..32c37ec 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -88,10 +88,10 @@ SPEC CHECKSUMS: SentryPrivate: b2f7996f37781080f04a946eb4e377ff63c64195 sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea sqlite3: 6e2d4a4879854d0ec86b476bf3c3e30870bac273 - sqlite3_flutter_libs: 5b7e226d522d67be60d7ade93f5aa11ebc0cd796 + sqlite3_flutter_libs: a25f3a0f522fdcd8fef6a4a50a3d681dd43d8dea url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 window_size: 339dafa0b27a95a62a843042038fa6c3c48de195 PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 -COCOAPODS: 1.12.1 +COCOAPODS: 1.14.3 From 3132957a91c3a403dc32557b4ef184a6377b0c35 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 14 Dec 2023 15:22:09 +0100 Subject: [PATCH 159/177] Fix --- .../settings/access_settings/access_settings.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/screens/settings/access_settings/access_settings.dart b/lib/screens/settings/access_settings/access_settings.dart index d5673f7..9c1e986 100644 --- a/lib/screens/settings/access_settings/access_settings.dart +++ b/lib/screens/settings/access_settings/access_settings.dart @@ -49,6 +49,7 @@ class _AccessSettingsState extends State with TickerProviderStat handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), sliver: SliverSafeArea( top: false, + bottom: false, sliver: SliverAppBar( title: Text(AppLocalizations.of(context)!.accessSettings), pinned: true, @@ -56,10 +57,7 @@ class _AccessSettingsState extends State with TickerProviderStat centerTitle: false, forceElevated: innerBoxIsScrolled, surfaceTintColor: isDesktop(width) ? Colors.transparent : null, - bottom: PreferredSize( - preferredSize: const Size(double.maxFinite, 50), - child: _Tabs(tabController: _tabController) - ) + bottom: _Tabs(tabController: _tabController) ), ), ) @@ -92,7 +90,7 @@ class _AccessSettingsState extends State with TickerProviderStat } } -class _Tabs extends StatelessWidget { +class _Tabs extends StatelessWidget implements PreferredSizeWidget { final TabController tabController; const _Tabs({ @@ -137,6 +135,9 @@ class _Tabs extends StatelessWidget { ] ); } + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); } class _TabsView extends StatelessWidget { From 31a37986bf8bd2fa719314624622cecf973a4e84 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 14 Dec 2023 15:24:13 +0100 Subject: [PATCH 160/177] Updated app version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 8672c5a..9bd3484 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.13.0-beta.1+116 +version: 2.13.0+117 environment: sdk: '>=2.18.1 <3.0.0' From a3620f259fc15710d990f38278fc6d34553bb948 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 15 Dec 2023 03:03:33 +0100 Subject: [PATCH 161/177] Updated iOS stuff --- ios/.gitignore | 0 ios/Flutter/AppFrameworkInfo.plist | 0 ios/Flutter/Debug.xcconfig | 0 ios/Flutter/Release.xcconfig | 0 ios/Podfile | 0 ios/Podfile.lock | 0 ios/Runner.xcodeproj/project.pbxproj | 41 ++++++-- .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 ios/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../Icon-App-1024x1024@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-50x50@1x.png | Bin .../AppIcon.appiconset/Icon-App-50x50@2x.png | Bin .../AppIcon.appiconset/Icon-App-57x57@1x.png | Bin .../AppIcon.appiconset/Icon-App-57x57@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-72x72@1x.png | Bin .../AppIcon.appiconset/Icon-App-72x72@2x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../Icon-App-83.5x83.5@2x.png | Bin .../LaunchBackground.imageset/Contents.json | 0 .../LaunchBackground.imageset/background.png | Bin .../darkbackground.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../LaunchImage.imageset/LaunchImageDark.png | Bin .../LaunchImageDark@2x.png | Bin .../LaunchImageDark@3x.png | Bin .../LaunchImage.imageset/README.md | 0 ios/Runner/Base.lproj/LaunchScreen.storyboard | 0 ios/Runner/Base.lproj/Main.storyboard | 0 ios/Runner/Info.plist | 98 +++++++++--------- ios/Runner/Runner-Bridging-Header.h | 0 52 files changed, 83 insertions(+), 56 deletions(-) mode change 100644 => 100755 ios/.gitignore mode change 100644 => 100755 ios/Flutter/AppFrameworkInfo.plist mode change 100644 => 100755 ios/Flutter/Debug.xcconfig mode change 100644 => 100755 ios/Flutter/Release.xcconfig mode change 100644 => 100755 ios/Podfile mode change 100644 => 100755 ios/Podfile.lock mode change 100644 => 100755 ios/Runner.xcodeproj/project.pbxproj mode change 100644 => 100755 ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata mode change 100644 => 100755 ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist mode change 100644 => 100755 ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings mode change 100644 => 100755 ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme mode change 100644 => 100755 ios/Runner.xcworkspace/contents.xcworkspacedata mode change 100644 => 100755 ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist mode change 100644 => 100755 ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings mode change 100644 => 100755 ios/Runner/AppDelegate.swift mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json mode change 100644 => 100755 ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json mode change 100644 => 100755 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png mode change 100644 => 100755 ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md mode change 100644 => 100755 ios/Runner/Base.lproj/LaunchScreen.storyboard mode change 100644 => 100755 ios/Runner/Base.lproj/Main.storyboard mode change 100644 => 100755 ios/Runner/Info.plist mode change 100644 => 100755 ios/Runner/Runner-Bridging-Header.h diff --git a/ios/.gitignore b/ios/.gitignore old mode 100644 new mode 100755 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist old mode 100644 new mode 100755 diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig old mode 100644 new mode 100755 diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig old mode 100644 new mode 100755 diff --git a/ios/Podfile b/ios/Podfile old mode 100644 new mode 100755 diff --git a/ios/Podfile.lock b/ios/Podfile.lock old mode 100644 new mode 100755 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj old mode 100644 new mode 100755 index 4f766b9..00edeae --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -68,7 +68,6 @@ 65533F0C0783FDE34AE79B0A /* Pods-Runner.release.xcconfig */, 69C2CC4A6DE17506FC5C0F13 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -156,6 +155,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -325,6 +325,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -335,6 +336,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -343,7 +345,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -361,15 +363,22 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 38Z3B9TJTR; ENABLE_BITCODE = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "AdGuard Home"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager; + PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguardHomeManager; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; @@ -397,6 +406,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -407,6 +417,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -421,7 +432,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -452,6 +463,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -462,6 +474,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +483,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -490,16 +503,23 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 38Z3B9TJTR; ENABLE_BITCODE = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "AdGuard Home"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager; + PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguardHomeManager; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -513,15 +533,22 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 38Z3B9TJTR; ENABLE_BITCODE = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "AdGuard Home"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager; + PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguardHomeManager; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata old mode 100644 new mode 100755 diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist old mode 100644 new mode 100755 diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings old mode 100644 new mode 100755 diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme old mode 100644 new mode 100755 diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata old mode 100644 new mode 100755 diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist old mode 100644 new mode 100755 diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings old mode 100644 new mode 100755 diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md old mode 100644 new mode 100755 diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard old mode 100644 new mode 100755 diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard old mode 100644 new mode 100755 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist old mode 100644 new mode 100755 index f9aeb27..55fe6b7 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -1,53 +1,53 @@ - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - AdGuard Home Manager - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - adguard_home_manager - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - - UIStatusBarHidden - - + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + AdGuard Home + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + adguardHomeManager + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h old mode 100644 new mode 100755 From d2e882a0301d82526ab2bb657a66a3ecd247f915 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 17 Dec 2023 14:14:31 +0100 Subject: [PATCH 162/177] Link to logs screen from home screen --- lib/providers/logs_provider.dart | 8 +- lib/screens/home/chart.dart | 85 +++++++++++++------ lib/screens/home/home.dart | 32 +++++++ .../logs/filters/filter_status_modal.dart | 2 +- .../logs/filters/logs_filters_modal.dart | 6 +- lib/screens/logs/logs_list.dart | 4 +- lib/screens/logs/logs_list_appbar.dart | 2 +- 7 files changed, 107 insertions(+), 32 deletions(-) diff --git a/lib/providers/logs_provider.dart b/lib/providers/logs_provider.dart index 7861e6d..d9bc55f 100644 --- a/lib/providers/logs_provider.dart +++ b/lib/providers/logs_provider.dart @@ -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) { diff --git a/lib/screens/home/chart.dart b/lib/screens/home/chart.dart index ba97f79..22c3117 100644 --- a/lib/screens/home/chart.dart +++ b/lib/screens/home/chart.dart @@ -1,17 +1,19 @@ import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; import 'package:provider/provider.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 data; final String label; final String primaryValue; final String secondaryValue; final Color color; final int hoursInterval; + final void Function() onTapTitle; const HomeChart({ super.key, @@ -20,20 +22,28 @@ class HomeChart extends StatelessWidget { required this.primaryValue, required this.secondaryValue, required this.color, - required this.hoursInterval + required this.hoursInterval, + required this.onTapTitle, }); + @override + State createState() => _HomeChartState(); +} + +class _HomeChartState extends State { + bool _isHover = false; + @override Widget build(BuildContext context) { final appConfigProvider = Provider.of(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 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); } @@ -52,13 +62,40 @@ class HomeChart extends StatelessWidget { crossAxisAlignment: 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: widget.onTapTitle, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Text( + widget.label, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: _isHover + ? Theme.of(context).colorScheme.onSurfaceVariant + : Theme.of(context).colorScheme.onSurface, + ), + ), + ), + const SizedBox(width: 4), + Icon( + Icons.chevron_right_rounded, + size: 20, + color: _isHover + ? Theme.of(context).colorScheme.onSurfaceVariant + : Theme.of(context).colorScheme.onSurface, + ) + ], + ), ), ), ), @@ -67,18 +104,18 @@ class HomeChart extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( - primaryValue, + widget.primaryValue, style: TextStyle( - color: color, + color: widget.color, fontSize: 18, fontWeight: FontWeight.w500 ), ), Text( - secondaryValue, + widget.secondaryValue, style: TextStyle( fontSize: 12, - color: color + color: widget.color ), ) ], @@ -86,19 +123,19 @@ class HomeChart extends StatelessWidget { : Row( children: [ Text( - primaryValue, + widget.primaryValue, style: TextStyle( - color: color, + color: widget.color, fontSize: 18, fontWeight: FontWeight.w500 ), ), const SizedBox(width: 10), Text( - "($secondaryValue)", + "(${widget.secondaryValue})", style: TextStyle( fontSize: 12, - color: color + color: widget.color ), ) ], @@ -110,10 +147,10 @@ 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, ) ), diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index 9e5de00..e7ed5c0 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -14,6 +14,7 @@ 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/chart.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'; @@ -61,6 +62,7 @@ class _HomeState extends State { Widget build(BuildContext context) { final statusProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final logsProvider = Provider.of(context); final width = MediaQuery.of(context).size.width; @@ -143,6 +145,14 @@ class _HomeState extends State { 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); + }, ), ), FractionallySizedBox( @@ -154,6 +164,13 @@ class _HomeState extends State { 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); + }, ), ), FractionallySizedBox( @@ -165,6 +182,13 @@ class _HomeState extends State { 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); + }, ), ), FractionallySizedBox( @@ -176,6 +200,14 @@ class _HomeState extends State { 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); + }, ), ), ], diff --git a/lib/screens/logs/filters/filter_status_modal.dart b/lib/screens/logs/filters/filter_status_modal.dart index 88f782b..c08d9a8 100644 --- a/lib/screens/logs/filters/filter_status_modal.dart +++ b/lib/screens/logs/filters/filter_status_modal.dart @@ -34,7 +34,7 @@ class _FilterStatusModalState extends State { final logsProvider = Provider.of(context); void apply() async { - logsProvider.setSelectedResultStatus(selectedResultStatus); + logsProvider.setSelectedResultStatus(value: selectedResultStatus); Navigator.pop(context); } diff --git a/lib/screens/logs/filters/logs_filters_modal.dart b/lib/screens/logs/filters/logs_filters_modal.dart index 5e7e746..aec6b27 100644 --- a/lib/screens/logs/filters/logs_filters_modal.dart +++ b/lib/screens/logs/filters/logs_filters_modal.dart @@ -262,13 +262,13 @@ class _FiltersList extends StatelessWidget { FilterChip( selected: logsProvider.selectedResultStatus == "all", label: Text(AppLocalizations.of(context)!.all), - onSelected: (_) => logsProvider.setSelectedResultStatus("all") + onSelected: (_) => logsProvider.setSelectedResultStatus(value: "all") ), FilterChip( selected: logsProvider.selectedResultStatus == "processed" || logsProvider.selectedResultStatus == "whitelisted", label: Text(AppLocalizations.of(context)!.allowed), - onSelected: (_) => logsProvider.setSelectedResultStatus("processed") + onSelected: (_) => logsProvider.setSelectedResultStatus(value: "processed") ), FilterChip( selected: logsProvider.selectedResultStatus == "blocked" || @@ -276,7 +276,7 @@ class _FiltersList extends StatelessWidget { logsProvider.selectedResultStatus == "blocked_parental" || logsProvider.selectedResultStatus == "safe_search", label: Text(AppLocalizations.of(context)!.blocked), - onSelected: (_) => logsProvider.setSelectedResultStatus("blocked") + onSelected: (_) => logsProvider.setSelectedResultStatus(value: "blocked") ), ], ), diff --git a/lib/screens/logs/logs_list.dart b/lib/screens/logs/logs_list.dart index ac0596a..ced94ca 100644 --- a/lib/screens/logs/logs_list.dart +++ b/lib/screens/logs/logs_list.dart @@ -22,11 +22,11 @@ class LogsListWidget extends StatefulWidget { final void Function(Log) onLogSelected; const LogsListWidget({ - Key? key, + super.key, required this.twoColumns, required this.selectedLog, required this.onLogSelected, - }) : super(key: key); + }); @override State createState() => _LogsListWidgetState(); diff --git a/lib/screens/logs/logs_list_appbar.dart b/lib/screens/logs/logs_list_appbar.dart index f467d26..8aeb752 100644 --- a/lib/screens/logs/logs_list_appbar.dart +++ b/lib/screens/logs/logs_list_appbar.dart @@ -256,7 +256,7 @@ class LogsListAppBar extends StatelessWidget { clients: logsProvider.appliedFilters.clients ) ); - logsProvider.setSelectedResultStatus('all'); + logsProvider.setSelectedResultStatus(value: 'all'); logsProvider.fetchLogs( inOffset: 0, responseStatus: 'all' From 6523229ea3082e8f43875be21b39163926704b69 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 17 Dec 2023 14:14:58 +0100 Subject: [PATCH 163/177] Removed unused imports --- lib/screens/home/chart.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/screens/home/chart.dart b/lib/screens/home/chart.dart index 22c3117..d111519 100644 --- a/lib/screens/home/chart.dart +++ b/lib/screens/home/chart.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_html/flutter_html.dart'; import 'package:provider/provider.dart'; import 'package:adguard_home_manager/widgets/line_chart.dart'; From 71b870b42ff72ae55c7bbe927e74967be80eafa0 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 17 Dec 2023 14:32:46 +0100 Subject: [PATCH 164/177] Added placeholders no data charts --- lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/screens/home/chart.dart | 125 +++++++++++++++++++++--------------- lib/screens/home/home.dart | 4 ++ 4 files changed, 85 insertions(+), 52 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4fe20b3..60b94cc 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -713,5 +713,7 @@ "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" + "invalidValue": "Invalid value", + "noDataChart": "There's no data to display this chart.", + "noData": "No data" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index d3e0855..a7a9fd6 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -713,5 +713,7 @@ "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" + "invalidValue": "Valor no válido", + "noDataChart": "No hay datos para mostrar este gráfico.", + "noData": "No hay datos" } \ No newline at end of file diff --git a/lib/screens/home/chart.dart b/lib/screens/home/chart.dart index d111519..0093ae8 100644 --- a/lib/screens/home/chart.dart +++ b/lib/screens/home/chart.dart @@ -1,5 +1,6 @@ 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'; @@ -13,6 +14,7 @@ class HomeChart extends StatefulWidget { final Color color; final int hoursInterval; final void Function() onTapTitle; + final bool isDesktop; const HomeChart({ super.key, @@ -23,6 +25,7 @@ class HomeChart extends StatefulWidget { required this.color, required this.hoursInterval, required this.onTapTitle, + required this.isDesktop, }); @override @@ -58,7 +61,9 @@ class _HomeChartState extends State { ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: isEmpty + ? CrossAxisAlignment.center + : CrossAxisAlignment.start, children: [ Flexible( child: MouseRegion( @@ -68,7 +73,7 @@ class _HomeChartState extends State { child: GestureDetector( onTapDown: (_) => setState(() => _isHover = true), onTapUp: (_) => setState(() => _isHover = false), - onTap: widget.onTapTitle, + onTap: !isEmpty ? () => widget.onTapTitle () : null, child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -79,66 +84,64 @@ class _HomeChartState extends State { style: TextStyle( fontSize: 18, fontWeight: FontWeight.w500, - color: _isHover + color: _isHover && !isEmpty ? Theme.of(context).colorScheme.onSurfaceVariant : Theme.of(context).colorScheme.onSurface, ), ), ), - const SizedBox(width: 4), - Icon( - Icons.chevron_right_rounded, - size: 20, - color: _isHover - ? 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( - widget.primaryValue, - style: TextStyle( - color: widget.color, - fontSize: 18, - fontWeight: FontWeight.w500 - ), - ), - Text( - widget.secondaryValue, - style: TextStyle( - fontSize: 12, - color: widget.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( - widget.primaryValue, - style: TextStyle( - color: widget.color, - fontSize: 18, - fontWeight: FontWeight.w500 - ), - ), - const SizedBox(width: 10), - Text( - "(${widget.secondaryValue})", - style: TextStyle( - fontSize: 12, - color: widget.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, + ), ) + ], + ) ], ), ), @@ -153,6 +156,28 @@ class _HomeChartState extends State { 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 + ), + ) + ], + ), + ) ], ), ), diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index e7ed5c0..32cdb89 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -153,6 +153,7 @@ class _HomeState extends State { logsProvider.filterLogs(); appConfigProvider.setSelectedScreen(2); }, + isDesktop: width > 700, ), ), FractionallySizedBox( @@ -171,6 +172,7 @@ class _HomeState extends State { ); appConfigProvider.setSelectedScreen(2); }, + isDesktop: width > 700, ), ), FractionallySizedBox( @@ -189,6 +191,7 @@ class _HomeState extends State { ); appConfigProvider.setSelectedScreen(2); }, + isDesktop: width > 700, ), ), FractionallySizedBox( @@ -208,6 +211,7 @@ class _HomeState extends State { logsProvider.filterLogs(); appConfigProvider.setSelectedScreen(2); }, + isDesktop: width > 700, ), ), ], From 569ab7d569b5af48552743ea7c4c88eec76ba57b Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 17 Dec 2023 22:09:13 +0100 Subject: [PATCH 165/177] Added option to block and unblock clients from home screen --- lib/l10n/app_en.arb | 5 +- lib/l10n/app_es.arb | 5 +- lib/models/menu_option.dart | 2 +- lib/providers/clients_provider.dart | 22 +++++ .../clients/client/active_client_tile.dart | 8 +- .../clients/client/added_client_tile.dart | 10 +-- lib/screens/clients/search_clients.dart | 6 +- .../filters/filters_triple_column.dart | 4 +- lib/screens/filters/list_options_menu.dart | 10 +-- lib/screens/home/home.dart | 4 + lib/screens/home/top_items/row_item.dart | 2 +- .../home/top_items/top_items_lists.dart | 82 +++++++++++++++---- .../home/top_items/top_items_screen.dart | 4 +- .../home/top_items/top_items_section.dart | 4 +- lib/screens/logs/log_tile.dart | 12 +-- lib/widgets/options_menu.dart | 12 +-- 16 files changed, 139 insertions(+), 53 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 60b94cc..7354d9c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -715,5 +715,8 @@ "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" + "noData": "No data", + "unblockClient": "Unblock client", + "blockingClient": "Blocking client...", + "unblockingClient": "Unblocking client..." } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index a7a9fd6..78d518f 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -715,5 +715,8 @@ "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" + "noData": "No hay datos", + "unblockClient": "Desbloquear cliente", + "blockingClient": "Bloqueando cliente...", + "unblockingClient": "Desbloqueando cliente..." } \ No newline at end of file diff --git a/lib/models/menu_option.dart b/lib/models/menu_option.dart index b89f8d9..e898514 100644 --- a/lib/models/menu_option.dart +++ b/lib/models/menu_option.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class MenuOption { final IconData? icon; final String title; - final void Function(dynamic) action; + final void Function() action; final bool? disabled; const MenuOption({ diff --git a/lib/providers/clients_provider.dart b/lib/providers/clients_provider.dart index bfcf19d..36185cc 100644 --- a/lib/providers/clients_provider.dart +++ b/lib/providers/clients_provider.dart @@ -190,6 +190,16 @@ class ClientsProvider with ChangeNotifier { "blocked_hosts": clients!.clientsAllowedBlocked?.blockedHosts ?? [], }; + 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); } @@ -223,6 +233,18 @@ class ClientsProvider with ChangeNotifier { } } + 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 removeClientList(String client, AccessSettingsList type) async { Map> body = { "allowed_clients": clients!.clientsAllowedBlocked?.allowedClients ?? [], diff --git a/lib/screens/clients/client/active_client_tile.dart b/lib/screens/clients/client/active_client_tile.dart index 76c928c..7a7a825 100644 --- a/lib/screens/clients/client/active_client_tile.dart +++ b/lib/screens/clients/client/active_client_tile.dart @@ -28,11 +28,11 @@ class ActiveClientTile extends StatelessWidget { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: OptionsMenu( - options: [ + options: (_) => [ MenuOption( icon: Icons.copy_rounded, title: AppLocalizations.of(context)!.copyClipboard, - action: (_) => copyToClipboard( + action: () => copyToClipboard( value: client.name != '' ? client.name! : client.ip, @@ -99,11 +99,11 @@ class ActiveClientTile extends StatelessWidget { } else { return OptionsMenu( - options: [ + options: (_) => [ MenuOption( icon: Icons.copy_rounded, title: AppLocalizations.of(context)!.copyClipboard, - action: (_) => copyToClipboard( + action: () => copyToClipboard( value: client.name != '' ? client.name! : client.ip, diff --git a/lib/screens/clients/client/added_client_tile.dart b/lib/screens/clients/client/added_client_tile.dart index 4f4037e..8c10fd7 100644 --- a/lib/screens/clients/client/added_client_tile.dart +++ b/lib/screens/clients/client/added_client_tile.dart @@ -46,11 +46,11 @@ class _AddedClientTileState extends State { color: Colors.transparent, borderRadius: BorderRadius.circular(28), child: OptionsMenu( - options: [ + options: (_) => [ MenuOption( icon: Icons.copy_rounded, title: AppLocalizations.of(context)!.copyClipboard, - action: (_) => copyToClipboard( + action: () => copyToClipboard( value: widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), successMessage: AppLocalizations.of(context)!.copiedClipboard, ) @@ -164,16 +164,16 @@ class _AddedClientTileState extends State { } else { return OptionsMenu( - options: [ + options: (_) => [ if (widget.onEdit != null) MenuOption( title: AppLocalizations.of(context)!.seeDetails, icon: Icons.file_open_rounded, - action: (_) => widget.onEdit!(widget.client) + action: () => widget.onEdit!(widget.client) ), MenuOption( icon: Icons.copy_rounded, title: AppLocalizations.of(context)!.copyClipboard, - action: (_) => copyToClipboard( + action: () => copyToClipboard( value: widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), successMessage: AppLocalizations.of(context)!.copiedClipboard, ) diff --git a/lib/screens/clients/search_clients.dart b/lib/screens/clients/search_clients.dart index d8e1104..e5b1451 100644 --- a/lib/screens/clients/search_clients.dart +++ b/lib/screens/clients/search_clients.dart @@ -226,16 +226,16 @@ class _SearchClientsState extends State { itemCount: clientsScreen.length, padding: const EdgeInsets.only(bottom: 0), itemBuilder: (context, index) => OptionsMenu( - options: [ + options: (v) => [ MenuOption( icon: Icons.edit_rounded, title: AppLocalizations.of(context)!.edit, - action: (v) => openClientModal(v) + action: () => openClientModal(v) ), MenuOption( icon: Icons.delete_rounded, title: AppLocalizations.of(context)!.delete, - action: (v) => openDeleteModal(v) + action: () => openDeleteModal(v) ), ], value: clientsScreen[index], diff --git a/lib/screens/filters/filters_triple_column.dart b/lib/screens/filters/filters_triple_column.dart index 3401bac..7d15639 100644 --- a/lib/screens/filters/filters_triple_column.dart +++ b/lib/screens/filters/filters_triple_column.dart @@ -283,11 +283,11 @@ class FiltersTripleColumn extends StatelessWidget { ), ], child: OptionsMenu( - options: [ + options: (_) => [ MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: (_) => copyToClipboard( + action: () => copyToClipboard( value: filteringProvider.filtering!.userRules[index], successMessage: AppLocalizations.of(context)!.copiedClipboard, ) diff --git a/lib/screens/filters/list_options_menu.dart b/lib/screens/filters/list_options_menu.dart index 32e4610..c35d080 100644 --- a/lib/screens/filters/list_options_menu.dart +++ b/lib/screens/filters/list_options_menu.dart @@ -146,7 +146,7 @@ class ListOptionsMenu extends StatelessWidget { color: Colors.transparent, child: InkWell( child: OptionsMenu( - options: [ + options: (_) => [ MenuOption( title: list.enabled == true ? AppLocalizations.of(context)!.disable @@ -154,12 +154,12 @@ class ListOptionsMenu extends StatelessWidget { icon: list.enabled == true ? Icons.gpp_bad_rounded : Icons.verified_user_rounded, - action: (_) => enableDisable() + action: () => enableDisable() ), MenuOption( title: AppLocalizations.of(context)!.copyListUrl, icon: Icons.copy_rounded, - action: (_) => copyToClipboard( + action: () => copyToClipboard( value: list.url, successMessage: AppLocalizations.of(context)!.listUrlCopied ) @@ -167,12 +167,12 @@ class ListOptionsMenu extends StatelessWidget { MenuOption( title: AppLocalizations.of(context)!.openListUrl, icon: Icons.open_in_browser_rounded, - action: (_) => openUrl(list.url) + action: () => openUrl(list.url) ), MenuOption( title: AppLocalizations.of(context)!.selectionMode, icon: Icons.check_rounded, - action: (_) => Future.delayed( + action: () => Future.delayed( const Duration(milliseconds: 0), () => openSelectionMode() ) diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index 32cdb89..f34a186 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -14,6 +14,7 @@ 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/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'; @@ -39,6 +40,9 @@ class _HomeState extends State { withLoadingIndicator: statusProvider.serverStatus != null ? false : true ); + final clientsProvider = Provider.of(context, listen: false); + clientsProvider.fetchClients(updateLoading: false); + super.initState(); isVisible = true; diff --git a/lib/screens/home/top_items/row_item.dart b/lib/screens/home/top_items/row_item.dart index 569d30c..b323eb0 100644 --- a/lib/screens/home/top_items/row_item.dart +++ b/lib/screens/home/top_items/row_item.dart @@ -16,7 +16,7 @@ class RowItem extends StatefulWidget { final bool clients; final bool showColor; final String? unit; - final List options; + final List Function(dynamic) options; final void Function(dynamic)? onTapEntry; const RowItem({ diff --git a/lib/screens/home/top_items/top_items_lists.dart b/lib/screens/home/top_items/top_items_lists.dart index 7f230fb..758a0b6 100644 --- a/lib/screens/home/top_items/top_items_lists.dart +++ b/lib/screens/home/top_items/top_items_lists.dart @@ -6,6 +6,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/home/top_items/top_items_section.dart'; +import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/functions/number_format.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -30,6 +31,7 @@ class TopItemsLists extends StatelessWidget { final statusProvider = Provider.of(context); final appConfigProvider = Provider.of(context); final logsProvider = Provider.of(context); + final clientsProvider = Provider.of(context); List bottom = [ Padding( @@ -96,10 +98,53 @@ class TopItemsLists extends StatelessWidget { } } - void copyValueClipboard(value) { + void copyValueClipboard(dynamic value) { copyToClipboard(value: value, successMessage: AppLocalizations.of(context)!.copiedClipboard); } + void blockUnblockClient(dynamic client) async { + final currentList = clientsProvider.checkClientList(client); + final newList = currentList == AccessSettingsList.allowed || currentList == null + ? AccessSettingsList.disallowed + : AccessSettingsList.allowed; + + ProcessModal processModal = ProcessModal(); + processModal.open( + currentList == AccessSettingsList.allowed || currentList == null + ? AppLocalizations.of(context)!.blockingClient + : AppLocalizations.of(context)!.unblockingClient + ); + + final result = await clientsProvider.addClientList(client, newList); + if (!context.mounted) return; + + processModal.close(); + + if (result.successful == true) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.clientAddedSuccessfully, + color: Colors.green + ); + } + else if (result.successful == false && result.content == 'client_another_list') { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.clientAnotherList, + color: Colors.red + ); + } + else { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: newList == AccessSettingsList.allowed || newList == AccessSettingsList.disallowed + ? AppLocalizations.of(context)!.clientNotRemoved + : AppLocalizations.of(context)!.domainNotAdded, + color: Colors.red + ); + } + } + return Column( children: order.asMap().entries.map((item) { switch (item.value) { @@ -113,16 +158,16 @@ class TopItemsLists extends StatelessWidget { withChart: true, withProgressBar: true, buildValue: (v) => v.toString(), - menuOptions: [ + menuOptions: (v) => [ MenuOption( title: AppLocalizations.of(context)!.blockDomain, icon: Icons.block_rounded, - action: (v) => blockUnblock(domain: v.toString(), newStatus: 'block') + action: () => blockUnblock(domain: v.toString(), newStatus: 'block') ), MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: copyValueClipboard + action: () => copyValueClipboard(v) ), ], onTapEntry: (v) => filterDomainLogs(value: v.toString()), @@ -141,16 +186,16 @@ class TopItemsLists extends StatelessWidget { withChart: true, withProgressBar: true, buildValue: (v) => v.toString(), - menuOptions: [ + menuOptions: (v) => [ MenuOption( title: AppLocalizations.of(context)!.unblockDomain, icon: Icons.check_rounded, - action: (v) => blockUnblock(domain: v, newStatus: 'unblock') + action: () => blockUnblock(domain: v, newStatus: 'unblock') ), MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: copyValueClipboard + action: () => copyValueClipboard(v) ) ], onTapEntry: (v) => filterDomainLogs(value: v), @@ -169,12 +214,21 @@ class TopItemsLists extends StatelessWidget { withChart: true, withProgressBar: true, buildValue: (v) => v.toString(), - menuOptions: [ + menuOptions: (v) => [ + if (clientsProvider.clients?.clientsAllowedBlocked != null) MenuOption( + title: clientsProvider.checkClientList(v) == AccessSettingsList.allowed || clientsProvider.checkClientList(v) == null + ? AppLocalizations.of(context)!.blockClient + : AppLocalizations.of(context)!.unblockClient, + icon: clientsProvider.checkClientList(v) == AccessSettingsList.allowed || clientsProvider.checkClientList(v) == null + ? Icons.block_rounded + : Icons.check_rounded, + action: () => blockUnblockClient(v) + ), MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: copyValueClipboard - ) + action: () => copyValueClipboard(v) + ), ], onTapEntry: (v) => filterClientLogs(value: v), ), @@ -193,11 +247,11 @@ class TopItemsLists extends StatelessWidget { withChart: true, withProgressBar: true, buildValue: (v) => v.toString(), - menuOptions: [ + menuOptions: (v) => [ MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: copyValueClipboard + action: () => copyValueClipboard(v) ) ], ), @@ -217,11 +271,11 @@ class TopItemsLists extends StatelessWidget { withChart: false, withProgressBar: false, buildValue: (v) => "${doubleFormat(v*1000, Platform.localeName)} ms", - menuOptions: [ + menuOptions: (v) => [ MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: copyValueClipboard + action: () => copyValueClipboard(v) ) ], ), diff --git a/lib/screens/home/top_items/top_items_screen.dart b/lib/screens/home/top_items/top_items_screen.dart index f2f9b4d..1070953 100644 --- a/lib/screens/home/top_items/top_items_screen.dart +++ b/lib/screens/home/top_items/top_items_screen.dart @@ -24,7 +24,7 @@ class TopItemsScreen extends StatefulWidget { final List> data; final bool withProgressBar; final String Function(dynamic) buildValue; - final List options; + final List Function(dynamic) options; final void Function(dynamic)? onTapEntry; final bool isFullscreen; @@ -263,7 +263,7 @@ class _TopItemsScreenState extends State { class _Content extends StatelessWidget { final List> screenData; final bool? isClient; - final List options; + final List Function(dynamic) options; final bool withProgressBar; final void Function(dynamic)? onTapEntry; final String Function(dynamic) buildValue; diff --git a/lib/screens/home/top_items/top_items_section.dart b/lib/screens/home/top_items/top_items_section.dart index f084b3f..bc021cf 100644 --- a/lib/screens/home/top_items/top_items_section.dart +++ b/lib/screens/home/top_items/top_items_section.dart @@ -21,7 +21,7 @@ class TopItemsSection extends StatefulWidget { final bool withChart; final bool withProgressBar; final String Function(dynamic) buildValue; - final List menuOptions; + final List Function(dynamic) menuOptions; final void Function(dynamic)? onTapEntry; const TopItemsSection({ @@ -350,7 +350,7 @@ class _ItemsList extends StatelessWidget { final HomeTopItems type; final bool showChart; final String Function(dynamic) buildValue; - final List menuOptions; + final List Function(dynamic) menuOptions; final void Function(dynamic)? onTapEntry; const _ItemsList({ diff --git a/lib/screens/logs/log_tile.dart b/lib/screens/logs/log_tile.dart index af130e2..6f4ea6b 100644 --- a/lib/screens/logs/log_tile.dart +++ b/lib/screens/logs/log_tile.dart @@ -128,7 +128,7 @@ class LogTile extends StatelessWidget { child: OptionsMenu( onTap: (_) => onLogTap(log), borderRadius: BorderRadius.circular(28), - options: [ + options: (v) => [ if (log.question.name != null) MenuOption( title: domainBlocked == true ? AppLocalizations.of(context)!.unblockDomain @@ -136,7 +136,7 @@ class LogTile extends StatelessWidget { icon: domainBlocked == true ? Icons.check_rounded : Icons.block_rounded, - action: (_) => blockUnblock( + action: () => blockUnblock( domain: log.question.name!, newStatus: domainBlocked == true ? 'unblock' : 'block' ) @@ -144,7 +144,7 @@ class LogTile extends StatelessWidget { MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: (v) => copyToClipboard(value: v, successMessage: AppLocalizations.of(context)!.copiedClipboard) + action: () => copyToClipboard(value: v, successMessage: AppLocalizations.of(context)!.copiedClipboard) ) ], child: Container( @@ -306,7 +306,7 @@ class LogTile extends StatelessWidget { color: Colors.transparent, child: OptionsMenu( onTap: (_) => onLogTap(log), - options: [ + options: (_) => [ if (log.question.name != null) MenuOption( title: domainBlocked == true ? AppLocalizations.of(context)!.unblockDomain @@ -314,7 +314,7 @@ class LogTile extends StatelessWidget { icon: domainBlocked == true ? Icons.check_rounded : Icons.block_rounded, - action: (_) => blockUnblock( + action: () => blockUnblock( domain: log.question.name!, newStatus: domainBlocked == true ? 'unblock' : 'block' ) @@ -322,7 +322,7 @@ class LogTile extends StatelessWidget { if (log.question.name != null) MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: (_) => copyToClipboard( + action: () => copyToClipboard( value: log.question.name!, successMessage: AppLocalizations.of(context)!.copiedClipboard ) diff --git a/lib/widgets/options_menu.dart b/lib/widgets/options_menu.dart index 861edab..ad91b33 100644 --- a/lib/widgets/options_menu.dart +++ b/lib/widgets/options_menu.dart @@ -11,7 +11,7 @@ import 'package:adguard_home_manager/models/menu_option.dart'; class OptionsMenu extends StatelessWidget { final Widget child; - final List options; + final List Function(dynamic) options; final dynamic value; final BorderRadius? borderRadius; final void Function(dynamic)? onTap; @@ -40,11 +40,11 @@ class OptionsMenu extends StatelessWidget { return Material( color: Colors.transparent, child: ContextMenuArea( - builder: (context) => options.map((opt) => CustomListTile( + builder: (context) => options(value).map((opt) => CustomListTile( title: opt.title, icon: opt.icon, onTap: () { - opt.action(value); + opt.action(); Navigator.pop(context); }, )).toList(), @@ -64,7 +64,7 @@ class OptionsMenu extends StatelessWidget { } class _OptionsModal extends StatelessWidget { - final List options; + final List Function(dynamic) options; final dynamic value; const _OptionsModal({ @@ -98,12 +98,12 @@ class _OptionsModal extends StatelessWidget { ), child: SingleChildScrollView( child: Wrap( - children: options.map((opt) => CustomListTileDialog( + children: options(value).map((opt) => CustomListTileDialog( title: opt.title, icon: opt.icon, onTap: () { Navigator.pop(context); - opt.action(value); + opt.action(); }, )).toList() ), From 313b76740a627eef4b67497a0e0ead63f6b9f6b3 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 18 Dec 2023 02:30:32 +0100 Subject: [PATCH 166/177] Added cache configuration and changed added client validation --- lib/l10n/app_en.arb | 9 +- lib/l10n/app_es.arb | 9 +- lib/models/clients.dart | 10 +- .../client/blocked_services_section.dart | 10 +- lib/screens/clients/client/client_form.dart | 109 ++++---- lib/screens/clients/client/client_screen.dart | 255 +++++++++++++----- .../client/client_screen_functions.dart | 22 +- .../clients/client/identifiers_section.dart | 16 +- lib/screens/clients/client/settings_tile.dart | 9 +- lib/screens/clients/client/tags_section.dart | 6 +- .../client/upstream_servers_section.dart | 14 +- 11 files changed, 302 insertions(+), 167 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7354d9c..6bf0c27 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -718,5 +718,12 @@ "noData": "No data", "unblockClient": "Unblock client", "blockingClient": "Blocking client...", - "unblockingClient": "Unblocking 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" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 78d518f..a6a222b 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -718,5 +718,12 @@ "noData": "No hay datos", "unblockClient": "Desbloquear cliente", "blockingClient": "Bloqueando cliente...", - "unblockingClient": "Desbloqueando 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" } \ No newline at end of file diff --git a/lib/models/clients.dart b/lib/models/clients.dart index 5808391..67740bd 100644 --- a/lib/models/clients.dart +++ b/lib/models/clients.dart @@ -89,6 +89,8 @@ class Client { final SafeSearch? safeSearch; final bool? ignoreQuerylog; final bool? ignoreStatistics; + final bool? upstreamsCacheEnabled; + final int? upstreamsCacheSize; Client({ required this.name, @@ -104,6 +106,8 @@ class Client { required this.safeSearch, required this.ignoreQuerylog, required this.ignoreStatistics, + required this.upstreamsCacheEnabled, + required this.upstreamsCacheSize, }); factory Client.fromJson(Map json) => Client( @@ -121,7 +125,9 @@ class Client { ? SafeSearch.fromJson(json["safe_search"]) : null, ignoreQuerylog: json["ignore_querylog"], - ignoreStatistics: json["ignore_statistics"] + ignoreStatistics: json["ignore_statistics"], + upstreamsCacheEnabled: json["upstreams_cache_enabled"], + upstreamsCacheSize: json["upstreams_cache_size"] ); Map toJson() => { @@ -138,5 +144,7 @@ class Client { "use_global_settings": useGlobalSettings, "ignore_querylog": ignoreQuerylog, "ignore_statistics": ignoreStatistics, + "upstreams_cache_enabled": upstreamsCacheEnabled, + "upstreams_cache_size": upstreamsCacheSize }; } \ No newline at end of file diff --git a/lib/screens/clients/client/blocked_services_section.dart b/lib/screens/clients/client/blocked_services_section.dart index f6effd4..4a7fc11 100644 --- a/lib/screens/clients/client/blocked_services_section.dart +++ b/lib/screens/clients/client/blocked_services_section.dart @@ -22,7 +22,7 @@ class BlockedServicesSection extends StatelessWidget { return Column( children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), + padding: const EdgeInsets.symmetric(horizontal: 16), child: Material( color: Theme.of(context).colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(28), @@ -31,8 +31,8 @@ class BlockedServicesSection extends StatelessWidget { borderRadius: BorderRadius.circular(28), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 5 + horizontal: 16, + vertical: 6 ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -56,7 +56,7 @@ class BlockedServicesSection extends StatelessWidget { ), ), ), - const SizedBox(height: 10), + const SizedBox(height: 12), Material( color: Colors.transparent, child: InkWell( @@ -69,7 +69,7 @@ class BlockedServicesSection extends StatelessWidget { : null, child: Padding( padding: const EdgeInsets.symmetric( - vertical: 8, horizontal: 24 + vertical: 8, horizontal: 32 ), child: Row( children: [ diff --git a/lib/screens/clients/client/client_form.dart b/lib/screens/clients/client/client_form.dart index 5c164af..e127466 100644 --- a/lib/screens/clients/client/client_form.dart +++ b/lib/screens/clients/client/client_form.dart @@ -1,6 +1,3 @@ -import 'dart:io'; - -import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -12,6 +9,7 @@ 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'; @@ -22,7 +20,6 @@ class ClientForm extends StatelessWidget { final bool isFullScreen; final Client? client; final TextEditingController nameController; - final void Function(bool) updateValidValues; final List identifiersControllers; final List selectedTags; final bool useGlobalSettingsFiltering; @@ -50,13 +47,17 @@ class ClientForm extends StatelessWidget { 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.updateValidValues, required this.identifiersControllers, required this.selectedTags, required this.useGlobalSettingsFiltering, @@ -84,26 +85,24 @@ class ClientForm extends StatelessWidget { 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 ListView( - padding: const EdgeInsets.only(top: 0), + return Column( children: [ - if (isFullScreen == true) const SizedBox(height: 24), - if (isFullScreen == false) const SizedBox(height: 6), + const SizedBox(height: 8), Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), + padding: const EdgeInsets.symmetric(horizontal: 16), child: TextFormField( enabled: client != null ? false : true, controller: nameController, - onChanged: (_) => updateValidValues( - checkValidValues( - identifiersControllers: identifiersControllers, - nameController: nameController - ) - ), + onChanged: (_) => {}, decoration: InputDecoration( prefixIcon: const Icon(Icons.badge_rounded), border: const OutlineInputBorder( @@ -117,7 +116,7 @@ class ClientForm extends StatelessWidget { ), SectionLabel( label: AppLocalizations.of(context)!.tags, - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), ), TagsSection( selectedTags: selectedTags, @@ -127,28 +126,17 @@ class ClientForm extends StatelessWidget { identifiersControllers: identifiersControllers, onUpdateIdentifiersControllers: (c) { updateIdentifiersControllers(c); - updateValidValues( - checkValidValues( - nameController: nameController, - identifiersControllers: identifiersControllers - ) - ); }, - onCheckValidValues: () => updateValidValues( - checkValidValues( - identifiersControllers: identifiersControllers, - nameController: nameController - ) - ), + onCheckValidValues: () => {} ), SectionLabel( label: AppLocalizations.of(context)!.settings, padding: const EdgeInsets.only( - left: 24, right: 24, top: 12, bottom: 24 + left: 16, right: 16, top: 12, bottom: 24 ) ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), + padding: const EdgeInsets.symmetric(horizontal: 16), child: Material( color: Theme.of(context).colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(28), @@ -157,8 +145,8 @@ class ClientForm extends StatelessWidget { borderRadius: BorderRadius.circular(28), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 5 + horizontal: 16, + vertical: 6 ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -182,7 +170,7 @@ class ClientForm extends StatelessWidget { ), ), ), - const SizedBox(height: 10), + const SizedBox(height: 8), SettingsTile( label: AppLocalizations.of(context)!.enableFiltering, value: enableFiltering, @@ -204,7 +192,7 @@ class ClientForm extends StatelessWidget { CustomListTile( title: AppLocalizations.of(context)!.safeSearch, padding: const EdgeInsets.symmetric( - horizontal: 42, + horizontal: 34, vertical: 16 ), trailing: Padding( @@ -228,15 +216,15 @@ class ClientForm extends StatelessWidget { ), SectionLabel( label: AppLocalizations.of(context)!.queryLogsAndStatistics, - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), ), CustomSwitchListTile( title: AppLocalizations.of(context)!.ignoreClientQueryLog, value: ignoreClientQueryLog, onChanged: updateIgnoreClientQueryLog, padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 4 + horizontal: 16, + vertical: 6 ), ), CustomSwitchListTile( @@ -244,13 +232,13 @@ class ClientForm extends StatelessWidget { value: ignoreClientStatistics, onChanged: updateIgnoreClientStatistics, padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 4 + horizontal: 16, + vertical: 6 ), ), SectionLabel( label: AppLocalizations.of(context)!.blockedServices, - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), ), BlockedServicesSection( useGlobalSettingsServices: useGlobalSettingsServices, @@ -260,15 +248,40 @@ class ClientForm extends StatelessWidget { ), UpstreamServersSection( upstreamServers: upstreamServers, - onCheckValidValues: () => updateValidValues( - checkValidValues( - identifiersControllers: identifiersControllers, - nameController: nameController - ) - ), + onCheckValidValues: () => {}, onUpdateUpstreamServers: updateUpstreamServers ), - SizedBox(height: Platform.isIOS ? 48 : 24) + 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, + ), + ), ], ); } diff --git a/lib/screens/clients/client/client_screen.dart b/lib/screens/clients/client/client_screen.dart index 9a16e1a..f41de69 100644 --- a/lib/screens/clients/client/client_screen.dart +++ b/lib/screens/clients/client/client_screen.dart @@ -39,6 +39,8 @@ class ClientScreen extends StatefulWidget { } class _ClientScreenState extends State { + final _scrollController = ScrollController(); + final Uuid uuid = const Uuid(); bool validValues = false; @@ -76,6 +78,15 @@ class _ClientScreenState extends State { 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(() { @@ -125,6 +136,10 @@ class _ClientScreenState extends State { )).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(); } @@ -147,20 +162,37 @@ class _ClientScreenState extends State { upstreams: List.from(upstreamServers.map((e) => e.controller.text)), tags: selectedTags, ignoreQuerylog: _ignoreClientQueryLog, - ignoreStatistics: _ignoreClientStatistics + 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 actions() { return [ IconButton( - onPressed: validValues == true - ? () { - createClient(); - Navigator.pop(context); - } - : null, + onPressed: validateValues, icon: const Icon(Icons.save_rounded), tooltip: AppLocalizations.of(context)!.save, ), @@ -193,38 +225,52 @@ class _ClientScreenState extends State { actions: actions(), ), body: SafeArea( - child: ClientForm( - isFullScreen: true, - client: widget.client, - nameController: nameController, - updateValidValues: (v) => setState(() => validValues = v), - 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), + 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) + ), + ], ), ), ), @@ -264,38 +310,52 @@ class _ClientScreenState extends State { ), ), Flexible( - child: ClientForm( - isFullScreen: false, - client: widget.client, - nameController: nameController, - updateValidValues: (v) => setState(() => validValues = v), - 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), + 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) + ), + ], ), ) ], @@ -306,3 +366,56 @@ class _ClientScreenState extends State { } } +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 + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/client/client_screen_functions.dart b/lib/screens/clients/client/client_screen_functions.dart index 1f292c6..f0338a1 100644 --- a/lib/screens/clients/client/client_screen_functions.dart +++ b/lib/screens/clients/client/client_screen_functions.dart @@ -74,22 +74,6 @@ void openSafeSearchModal({ ); } -bool checkValidValues({ - required TextEditingController nameController, - required List identifiersControllers -}) { - if ( - nameController.text != '' && - identifiersControllers.isNotEmpty && - identifiersControllers[0].controller.text != '' - ) { - return true; - } - else { - return false; - } -} - void openClientFormModal({ required BuildContext context, required double width, @@ -123,4 +107,10 @@ void openClientFormModal({ onDelete: onDelete, ), ); +} + +bool validateNumber(String value) { + if (value == "") return true; + final regexp = RegExp(r'^\d+$'); + return regexp.hasMatch(value); } \ No newline at end of file diff --git a/lib/screens/clients/client/identifiers_section.dart b/lib/screens/clients/client/identifiers_section.dart index 91a56eb..c5f9d4c 100644 --- a/lib/screens/clients/client/identifiers_section.dart +++ b/lib/screens/clients/client/identifiers_section.dart @@ -11,11 +11,11 @@ class IdentifiersSection extends StatefulWidget { final void Function() onCheckValidValues; const IdentifiersSection({ - Key? key, + super.key, required this.identifiersControllers, required this.onUpdateIdentifiersControllers, required this.onCheckValidValues - }) : super(key: key); + }); @override State createState() => _IdentifiersSectionState(); @@ -34,11 +34,11 @@ class _IdentifiersSectionState extends State { SectionLabel( label: AppLocalizations.of(context)!.identifiers, padding: const EdgeInsets.only( - left: 24, right: 24, top: 24, bottom: 12 + left: 16, right: 16, top: 24, bottom: 12 ) ), Padding( - padding: const EdgeInsets.only(right: 20), + padding: const EdgeInsets.only(right: 10), child: IconButton( onPressed: () => widget.onUpdateIdentifiersControllers([ ...widget.identifiersControllers, @@ -54,7 +54,7 @@ class _IdentifiersSectionState extends State { ), if (widget.identifiersControllers.isNotEmpty) ...widget.identifiersControllers.map((controller) => Padding( padding: const EdgeInsets.only( - top: 12, bottom: 12, left: 24, right: 20 + top: 12, bottom: 12, left: 16, right: 10 ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, @@ -75,9 +75,9 @@ class _IdentifiersSectionState extends State { ), ), ), - const SizedBox(width: 16), + const SizedBox(width: 12), Padding( - padding: const EdgeInsets.only(bottom: 25), + padding: const EdgeInsets.only(bottom: 24), child: IconButton( onPressed: () => widget.onUpdateIdentifiersControllers( widget.identifiersControllers.where((e) => e.id != controller.id).toList() @@ -87,7 +87,7 @@ class _IdentifiersSectionState extends State { ) ], ), - )).toList(), + )), if (widget.identifiersControllers.isEmpty) Container( padding: const EdgeInsets.symmetric(vertical: 16), child: Text( diff --git a/lib/screens/clients/client/settings_tile.dart b/lib/screens/clients/client/settings_tile.dart index c2d6ede..9b26b2c 100644 --- a/lib/screens/clients/client/settings_tile.dart +++ b/lib/screens/clients/client/settings_tile.dart @@ -7,12 +7,12 @@ class SettingsTile extends StatelessWidget { final bool useGlobalSettingsFiltering; const SettingsTile({ - Key? key, + super.key, required this.label, required this.value, this.onChange, required this.useGlobalSettingsFiltering - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -23,10 +23,7 @@ class SettingsTile extends StatelessWidget { ? value != null ? () => onChange!(!value!) : null : null, child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 42, - vertical: 5 - ), + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 6), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/screens/clients/client/tags_section.dart b/lib/screens/clients/client/tags_section.dart index a6fa67c..8d63a0e 100644 --- a/lib/screens/clients/client/tags_section.dart +++ b/lib/screens/clients/client/tags_section.dart @@ -7,10 +7,10 @@ class TagsSection extends StatelessWidget { final void Function(List) onTagsSelected; const TagsSection({ - Key? key, + super.key, required this.selectedTags, required this.onTagsSelected - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -24,7 +24,7 @@ class TagsSection extends StatelessWidget { ) , child: Padding( padding: const EdgeInsets.symmetric( - vertical: 0, horizontal: 24 + vertical: 0, horizontal: 16 ), child: Row( children: [ diff --git a/lib/screens/clients/client/upstream_servers_section.dart b/lib/screens/clients/client/upstream_servers_section.dart index 97c8974..56fb7aa 100644 --- a/lib/screens/clients/client/upstream_servers_section.dart +++ b/lib/screens/clients/client/upstream_servers_section.dart @@ -11,11 +11,11 @@ class UpstreamServersSection extends StatefulWidget { final void Function(List) onUpdateUpstreamServers; const UpstreamServersSection({ - Key? key, + super.key, required this.upstreamServers, required this.onCheckValidValues, required this.onUpdateUpstreamServers - }) : super(key: key); + }); @override State createState() => _UpstreamServersSectionState(); @@ -33,10 +33,10 @@ class _UpstreamServersSectionState extends State { children: [ SectionLabel( label: AppLocalizations.of(context)!.upstreamServers, - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), ), Padding( - padding: const EdgeInsets.only(right: 20), + padding: const EdgeInsets.only(right: 12), child: IconButton( onPressed: () => setState(() => widget.upstreamServers.add( ControllerListItem( @@ -50,7 +50,7 @@ class _UpstreamServersSectionState extends State { ], ), if (widget.upstreamServers.isNotEmpty) ...widget.upstreamServers.map((controller) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), + padding: const EdgeInsets.only(left: 16, right: 12), child: Padding( padding: const EdgeInsets.only(bottom: 20), child: Row( @@ -71,7 +71,7 @@ class _UpstreamServersSectionState extends State { ), ), ), - const SizedBox(width: 16), + const SizedBox(width: 12), IconButton( onPressed: () => widget.onUpdateUpstreamServers( widget.upstreamServers.where((e) => e.id != controller.id).toList() @@ -81,7 +81,7 @@ class _UpstreamServersSectionState extends State { ], ), ), - )).toList(), + )), if (widget.upstreamServers.isEmpty) Container( padding: const EdgeInsets.symmetric(vertical: 16), child: Column( From b7943f53057b422b41d09e0865461b46fd010001 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 18 Dec 2023 02:31:09 +0100 Subject: [PATCH 167/177] Updated app version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 9bd3484..554b898 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.13.0+117 +version: 2.14.0-beta.1+118 environment: sdk: '>=2.18.1 <3.0.0' From 62daa56b1be646868cf39672b4066b7a01df0a6e Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 20 Dec 2023 15:45:42 +0100 Subject: [PATCH 168/177] Fix copy log domain --- lib/screens/logs/log_tile.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/screens/logs/log_tile.dart b/lib/screens/logs/log_tile.dart index 6f4ea6b..f5db5ee 100644 --- a/lib/screens/logs/log_tile.dart +++ b/lib/screens/logs/log_tile.dart @@ -128,7 +128,7 @@ class LogTile extends StatelessWidget { child: OptionsMenu( onTap: (_) => onLogTap(log), borderRadius: BorderRadius.circular(28), - options: (v) => [ + options: (_) => [ if (log.question.name != null) MenuOption( title: domainBlocked == true ? AppLocalizations.of(context)!.unblockDomain @@ -141,10 +141,10 @@ class LogTile extends StatelessWidget { newStatus: domainBlocked == true ? 'unblock' : 'block' ) ), - MenuOption( + if (log.question.name != null) MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: () => copyToClipboard(value: v, successMessage: AppLocalizations.of(context)!.copiedClipboard) + action: () => copyToClipboard(value: log.question.name!, successMessage: AppLocalizations.of(context)!.copiedClipboard) ) ], child: Container( From 565494e3f992582515b4f48ab4426169a246a58d Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 20 Dec 2023 15:47:12 +0100 Subject: [PATCH 169/177] Fix parse ttl client --- lib/screens/settings/dns/dns_server_settings.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/screens/settings/dns/dns_server_settings.dart b/lib/screens/settings/dns/dns_server_settings.dart index ba04422..b13f593 100644 --- a/lib/screens/settings/dns/dns_server_settings.dart +++ b/lib/screens/settings/dns/dns_server_settings.dart @@ -156,7 +156,7 @@ class _DnsServerSettingsScreenState extends State { "blocking_mode": blockingMode, "blocking_ipv4": ipv4controller.text, "blocking_ipv6": ipv6controller.text, - "blocked_response_ttl": int.parse(_ttlController.text) + "blocked_response_ttl": int.tryParse(_ttlController.text) }); processModal.close(); From 3c42f790bda6498e61c78bf1e7ec9c36f7202155 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 20 Dec 2023 15:50:39 +0100 Subject: [PATCH 170/177] Fix --- lib/services/api_client.dart | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index c2e1fca..b45622a 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -39,10 +39,17 @@ class ApiClientV2 { Future getServerVersion() async { final result = await HttpRequestClient.get(urlPath: '/status', server: server); if (result.successful == true) { - return ApiResponse( - successful: true, - content: jsonDecode(result.body!)['version'] - ); + try { + return ApiResponse( + successful: true, + content: jsonDecode(result.body!)['version'] + ); + } on FormatException { + return const ApiResponse(successful: false); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } } else { return const ApiResponse(successful: false); From af2b9f2704430c27493307ef645761d52258bdc1 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 20 Dec 2023 15:51:17 +0100 Subject: [PATCH 171/177] Updated app version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 554b898..5edc2c7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.14.0-beta.1+118 +version: 2.14.0+119 environment: sdk: '>=2.18.1 <3.0.0' From 9a1cefdc26aa9117552022ab34405f3f5f759e53 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 20 Dec 2023 18:23:52 +0100 Subject: [PATCH 172/177] Added redirect to https warning --- lib/l10n/app_en.arb | 3 ++- lib/l10n/app_es.arb | 3 ++- lib/widgets/add_server/add_server_modal.dart | 24 +++++++++++++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 6bf0c27..3778a60 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -725,5 +725,6 @@ "nameInvalid": "Name is required", "oneIdentifierRequired": "At least one identifier is required", "dnsCacheNumber": "DNS cache size must be a number", - "errors": "Errors" + "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." } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index a6a222b..03e3440 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -725,5 +725,6 @@ "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" + "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." } \ No newline at end of file diff --git a/lib/widgets/add_server/add_server_modal.dart b/lib/widgets/add_server/add_server_modal.dart index e30d1e0..ac7d4a1 100644 --- a/lib/widgets/add_server/add_server_modal.dart +++ b/lib/widgets/add_server/add_server_modal.dart @@ -127,6 +127,7 @@ class _AddServerModalState extends State { if (status == AuthStatus.manyAttepts) return AppLocalizations.of(context)!.tooManyAttempts; if (status == AuthStatus.socketException || status == AuthStatus.timeoutException) return AppLocalizations.of(context)!.cantReachServer; if (status == AuthStatus.serverError) return AppLocalizations.of(context)!.serverError; + if (status == AuthStatus.handshakeException) return AppLocalizations.of(context)!.sslError; return AppLocalizations.of(context)!.unknownError; } @@ -429,6 +430,24 @@ class _AddServerModalState extends State { horizontal: 24, ), ), + Card( + margin: const EdgeInsets.only( + top: 16, left: 24, right: 24 + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Icon( + Icons.warning_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 16), + Flexible(child: Text(AppLocalizations.of(context)!.redirectHttpsWarning)) + ], + ), + ), + ), if (connectionType == ConnectionType.https) Card( margin: const EdgeInsets.only( top: 16, left: 24, right: 24 @@ -437,7 +456,10 @@ class _AddServerModalState extends State { padding: const EdgeInsets.all(16), child: Row( children: [ - const Icon(Icons.info_rounded), + Icon( + Icons.info_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), const SizedBox(width: 16), Flexible(child: Text(AppLocalizations.of(context)!.sslWarning)) ], From 0980641746bc77bfea7c4a5696080bf2362fa130 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Tue, 16 Jan 2024 18:25:20 +0100 Subject: [PATCH 173/177] Updated libraries --- macos/Podfile.lock | 14 +++--- pubspec.lock | 120 ++++++++++++++++++++++++--------------------- pubspec.yaml | 2 +- 3 files changed, 72 insertions(+), 64 deletions(-) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 32c37ec..9d30918 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -9,13 +9,13 @@ PODS: - FMDB/standard (2.7.5) - package_info_plus (0.0.1): - FlutterMacOS - - Sentry/HybridSDK (8.15.2): - - SentryPrivate (= 8.15.2) + - Sentry/HybridSDK (8.17.2): + - SentryPrivate (= 8.17.2) - sentry_flutter (0.0.1): - Flutter - FlutterMacOS - - Sentry/HybridSDK (= 8.15.2) - - SentryPrivate (8.15.2) + - Sentry/HybridSDK (= 8.17.2) + - SentryPrivate (8.17.2) - sqflite (0.0.2): - FlutterMacOS - FMDB (>= 2.7.5) @@ -83,9 +83,9 @@ SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce - Sentry: 6f5742b4c47c17c9adcf265f6f328cf4a0ed1923 - sentry_flutter: 2c309a1d4b45e59d02cfa15795705687f1e2081b - SentryPrivate: b2f7996f37781080f04a946eb4e377ff63c64195 + Sentry: 64a9f9c3637af913adcf53deced05bbe452d1410 + sentry_flutter: 57912cf425e09398bdf47f38842a1fcb9836f1be + SentryPrivate: 024c6fed507ac39ae98e6d087034160f942920d5 sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea sqlite3: 6e2d4a4879854d0ec86b476bf3c3e30870bac273 sqlite3_flutter_libs: a25f3a0f522fdcd8fef6a4a50a3d681dd43d8dea diff --git a/pubspec.lock b/pubspec.lock index 963ce6f..cf61106 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: "direct main" description: name: animations - sha256: "708e4b68c23228c264b038fe7003a2f5d01ce85fc64d8cae090e86b27fcea6c5" + sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb url: "https://pub.dev" source: hosted - version: "2.0.10" + version: "2.0.11" ansicolor: dependency: transitive description: @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: archive - sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b" + sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" url: "https://pub.dev" source: hosted - version: "3.4.9" + version: "3.4.10" args: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: cli_util - sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7 + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.4.1" clock: dependency: transitive description: @@ -157,10 +157,10 @@ packages: dependency: "direct main" description: name: dynamic_color - sha256: "8b8bd1d798bd393e11eddeaa8ae95b12ff028bf7d5998fc5d003488cd5f4ce2f" + sha256: a866f1f8947bfdaf674d7928e769eac7230388a2e7a2542824fad4bb5b87be3b url: "https://pub.dev" source: hosted - version: "1.6.8" + version: "1.6.9" equatable: dependency: transitive description: @@ -201,14 +201,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" fl_chart: dependency: "direct main" description: name: fl_chart - sha256: "5a74434cc83bf64346efb562f1a06eefaf1bcb530dc3d96a104f631a1eff8d79" + sha256: fe6fec7d85975a99c73b9515a69a6e291364accfa0e4a5b3ce6de814d74b9a1c url: "https://pub.dev" source: hosted - version: "0.65.0" + version: "0.66.0" flutter: dependency: "direct main" description: flutter @@ -263,18 +271,18 @@ packages: dependency: "direct main" description: name: flutter_markdown - sha256: "35108526a233cc0755664d445f8a6b4b61e6f8fe993b3658b80b4a26827fc196" + sha256: "30088ce826b5b9cfbf9e8bece34c716c8a59fa54461dcae1e4ac01a94639e762" url: "https://pub.dev" source: hosted - version: "0.6.18+2" + version: "0.6.18+3" flutter_native_splash: dependency: "direct dev" description: name: flutter_native_splash - sha256: "141b20f15a2c4fe6e33c49257ca1bc114fc5c500b04fcbc8d75016bb86af672f" + sha256: "9cdb5d9665dab5d098dc50feab74301c2c228cd02ca25c9b546ab572cebcd6af" url: "https://pub.dev" source: hosted - version: "2.3.8" + version: "2.3.9" flutter_reorderable_list: dependency: "direct main" description: @@ -346,10 +354,10 @@ packages: dependency: transitive description: name: image - sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271" + sha256: "004a2e90ce080f8627b5a04aecb4cdfac87d2c3f3b520aa291260be5a32c033d" url: "https://pub.dev" source: hosted - version: "4.1.3" + version: "4.1.4" intl: dependency: "direct main" description: @@ -490,18 +498,18 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.1.8" pointycastle: dependency: transitive description: name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "3.7.4" provider: dependency: "direct main" description: @@ -522,18 +530,18 @@ packages: dependency: transitive description: name: sentry - sha256: e7ded42974bac5f69e4ca4ddc57d30499dd79381838f24b7e8fd9aa4139e7b79 + sha256: "89e426587b0879e53c46a0aae0eb312696d9d2d803ba14b252a65cc24b1416a2" url: "https://pub.dev" source: hosted - version: "7.13.2" + version: "7.14.0" sentry_flutter: dependency: "direct main" description: name: sentry_flutter - sha256: d6f55ec7a1f681784165021f749007712a72ff57eadf91e963331b6ae326f089 + sha256: fd089ee4e75a927be037c56815a0a54af5a519f52b803a5ffecb589bb36e2401 url: "https://pub.dev" source: hosted - version: "7.13.2" + version: "7.14.0" sky_engine: dependency: transitive description: flutter @@ -567,26 +575,26 @@ packages: dependency: transitive description: name: sqflite_common - sha256: bb4738f15b23352822f4c42a531677e5c6f522e079461fd240ead29d8d8a54a6 + sha256: "76db4d324c8cbb16ca5b60ad2f3d25cec953107c93ae65aafa480d3e6fb69f14" url: "https://pub.dev" source: hosted - version: "2.5.0+2" + version: "2.5.2-1" sqflite_common_ffi: dependency: "direct main" description: name: sqflite_common_ffi - sha256: "873677ee78738a723d1ded4ccb23980581998d873d30ee9c331f6a81748663ff" + sha256: d0e3f0d04fdf668e57db8db1df758f56c4193cb429092c708e7bfcc6ab04b27e url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" sqlite3: dependency: transitive description: name: sqlite3 - sha256: "8922805564b78eb7aa9386c10056d377a541ac7270dc6a1589176277ebb4d15d" + sha256: c4a4c5a4b2a32e2d0f6837b33d7c91a67903891a5b7dbe706cf4b1f6b0c798c5 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" sqlite3_flutter_libs: dependency: "direct main" description: @@ -631,10 +639,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.0+1" term_glyph: dependency: transitive description: @@ -671,34 +679,34 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86 + sha256: d25bb0ca00432a5e1ee40e69c36c85863addf7cc45e433769d61bed3fe81fd96 url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.2.3" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.2.2" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.4" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" url_launcher_macos: dependency: transitive description: @@ -711,58 +719,58 @@ packages: dependency: transitive description: name: url_launcher_platform_interface - sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.1" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9" + sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" uuid: dependency: "direct main" description: name: uuid - sha256: df5a4d8f22ee4ccd77f8839ac7cb274ebc11ef9adcce8b92be14b797fe889921 + sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 url: "https://pub.dev" source: hosted - version: "4.2.1" + version: "4.3.3" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_math: dependency: transitive description: @@ -783,10 +791,10 @@ packages: dependency: transitive description: name: win32 - sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 + sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "5.2.0" win32_registry: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5edc2c7..18406dd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,7 +48,7 @@ dependencies: device_info_plus: ^9.1.1 uuid: ^4.2.1 expandable: ^5.0.1 - fl_chart: ^0.65.0 + fl_chart: ^0.66.0 flutter_web_browser: ^0.17.1 flutter_svg: ^2.0.9 percent_indicator: ^4.2.3 From 26086269e5807382ba7dd943c18c8f5c73ab5ff9 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Tue, 16 Jan 2024 18:25:38 +0100 Subject: [PATCH 174/177] Fix save fallback dns when no dns is added --- lib/providers/dns_provider.dart | 2 +- lib/screens/settings/dns/fallback_dns.dart | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/providers/dns_provider.dart b/lib/providers/dns_provider.dart index c5809af..84b86cd 100644 --- a/lib/providers/dns_provider.dart +++ b/lib/providers/dns_provider.dart @@ -119,7 +119,7 @@ class DnsProvider with ChangeNotifier { if (result.successful == true) { DnsInfo data = dnsInfo!; - data.bootstrapDns = List.from(value['fallback_dns']); + data.fallbackDns = List.from(value['fallback_dns']); setDnsInfoData(data); return result; } diff --git a/lib/screens/settings/dns/fallback_dns.dart b/lib/screens/settings/dns/fallback_dns.dart index 4df30c9..dcae0a2 100644 --- a/lib/screens/settings/dns/fallback_dns.dart +++ b/lib/screens/settings/dns/fallback_dns.dart @@ -34,11 +34,7 @@ class _FallbackDnsScreenState extends State { } void checkValidValues() { - if ( - fallbackControllers.isNotEmpty && - fallbackControllers.every((element) => element['controller'].text != '') && - fallbackControllers.every((element) => element['error'] == null) - ) { + if (fallbackControllers.every((element) => element['error'] == null)) { setState(() => validValues = true); } else { From 61d521f9f9f805b8fdc221973a069701cbd7c339 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Tue, 16 Jan 2024 18:25:47 +0100 Subject: [PATCH 175/177] Change logs breaking width --- lib/screens/logs/logs.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/screens/logs/logs.dart b/lib/screens/logs/logs.dart index 757c949..560db9c 100644 --- a/lib/screens/logs/logs.dart +++ b/lib/screens/logs/logs.dart @@ -21,7 +21,7 @@ class _LogsState extends State { Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { - if (constraints.maxWidth > 1000) { + if (constraints.maxWidth > 800) { return Material( color: Colors.transparent, child: Row( From 236124d246e0850929258e739b2dbfd8eef27688 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Tue, 16 Jan 2024 18:45:37 +0100 Subject: [PATCH 176/177] Updated readme, added key properties sample and updated app version --- README.md | 42 +++++++++++++++++++++-------------- android/key.properties.sample | 4 ++++ pubspec.yaml | 2 +- 3 files changed, 30 insertions(+), 18 deletions(-) create mode 100644 android/key.properties.sample diff --git a/README.md b/README.md index 0a4af9d..a81e1dd 100644 --- a/README.md +++ b/README.md @@ -44,18 +44,28 @@ On [this repository](https://github.com/JuanRodenas/Pihole_list) you can find a ## Generate production build
    +
  • + Prerequisites +
      +
    1. Open pubspec.yaml and change the version name and the version number.
    2. +
    3. Run flutter clean.
    4. +
    5. Run flutter pub get.
    6. +
    +
  • +
  • + Android +
      +
    1. Make sure you have your key.properties file at android/, with all the required values of your signing key correctly set up.
    2. +
    3. Make sure you have your keystore file at android/app.
    4. +
    5. Run flutter build apk --release to compile the APK.
    6. +
    7. The .apk package is located at build/app/outputs/flutter-apk/app-release.apk.
    8. +
    +
  • macOS
      -
    1. flutter clean
    2. -
    3. flutter pub get
    4. -
    5. flutter build macos --release
    6. -
    7. Open macos/Runner.xcworkspace on Xcode
    8. -
    9. Make sure all the pods have the minimum deployment version at 10.14
    10. -
    11. Select Runner > Targets Runner
    12. -
    13. Make sure the Version and Build numbers are correct
    14. -
    15. Click on Product menu and on Archive
    16. -
    17. Select the first on the list and click on Distribute app, select Copy App and click on Next
    18. +
    19. Run flutter build macos --release to compile the production build.
    20. +
    21. The .app package is located at build/macos/Build/Products/Release/AdGuard Home Manager.app.
  • @@ -67,24 +77,22 @@ On [this repository](https://github.com/JuanRodenas/Pihole_list) you can find a Build
      -
    1. Open debian.yaml file inside debian/ and update the version number
    2. +
    3. Open debian.yaml file inside debian/ and update the version number
    4. run rps build linux
    5. -
    6. The .tar.gz is at build/linux/x64/release/bundle
    7. -
    8. The .deb package is at debian/packages
    9. +
    10. The .tar.gz is at build/linux/x64/release/bundle
    11. +
    12. The .deb package is at debian/packages
  • Windows
      -
    1. flutter clean
    2. -
    3. flutter pub get
    4. -
    5. flutter build windows
    6. +
    7. Run flutter build windows --release.
    8. Open Inno Setup Compiler application and load the script
    9. -
    10. The script is located at windows/innosetup_installer_builder.iss
    11. +
    12. The script is located at windows/innosetup_installer_builder.iss
    13. Update the version number and save the changes
    14. Click on the Compile button
    15. -
    16. The installer will be generated at build/windows/aghm_installer.exe
    17. +
    18. The installer will be generated at build/windows/aghm_installer.exe.
  • diff --git a/android/key.properties.sample b/android/key.properties.sample new file mode 100644 index 0000000..b51dc2c --- /dev/null +++ b/android/key.properties.sample @@ -0,0 +1,4 @@ +storePassword= # keystore password # +keyPassword= # keystore key password # +keyAlias= # key alias # +storeFile= # ./keystore-file-name.jks # \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 18406dd..e4dbaa8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.14.0+119 +version: 2.14.1+120 environment: sdk: '>=2.18.1 <3.0.0' From d93eb504b069ec1999c4371e51370a1ea69d2483 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Tue, 16 Jan 2024 19:06:47 +0100 Subject: [PATCH 177/177] Updated workflow --- .github/workflows/release-stable.yaml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-stable.yaml b/.github/workflows/release-stable.yaml index 2d8f0e8..220c56d 100644 --- a/.github/workflows/release-stable.yaml +++ b/.github/workflows/release-stable.yaml @@ -172,7 +172,7 @@ jobs: - name: Generate .deb package run: flutter_to_debian - name: Move .deb package to project root - run: mv debian/packages/AdGuardHomeManager_${{ env.VERSION_NAME }}_amd64.deb AdGuardHomeManager_${{ env.VERSION_NAME }}_Linux_amd64.deb + run: mv build/linux/x64/release/debian/AdGuardHomeManager_${{ env.VERSION_NAME }}_amd64.deb AdGuardHomeManager_${{ env.VERSION_NAME }}_Linux_amd64.deb - name: Generate .tar.gz package uses: a7ul/tar-action@v1.1.3 id: compress diff --git a/README.md b/README.md index a81e1dd..930d090 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ On [this repository](https://github.com/JuanRodenas/Pihole_list) you can find a
  • Open debian.yaml file inside debian/ and update the version number
  • run rps build linux
  • The .tar.gz is at build/linux/x64/release/bundle
  • -
  • The .deb package is at debian/packages
  • +
  • The .deb package is at build/linux/x64/release/debian/