Merge branch 'beta'

This commit is contained in:
Juan Gilsanz Polo 2023-11-26 22:53:24 +01:00
commit 6ead07e464
37 changed files with 1140 additions and 979 deletions

View file

@ -5,7 +5,9 @@ import 'package:adguard_home_manager/constants/enums.dart';
final List<HomeTopItems> homeTopItemsDefaultOrder = [
HomeTopItems.queriedDomains,
HomeTopItems.blockedDomains,
HomeTopItems.recurrentClients
HomeTopItems.recurrentClients,
HomeTopItems.topUpstreams,
HomeTopItems.avgUpstreamResponseTime
];
final String homeTopItemsDefaultOrderString = jsonEncode(

View file

@ -1,2 +1,2 @@
enum LoadStatus { loading, loaded, error }
enum HomeTopItems { queriedDomains, blockedDomains, recurrentClients }
enum HomeTopItems { queriedDomains, blockedDomains, recurrentClients, topUpstreams, avgUpstreamResponseTime }

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -9,6 +9,8 @@ class DnsStatistics {
final List<Map<String, int>> topQueriedDomains;
final List<Map<String, int>> topClients;
final List<Map<String, int>> topBlockedDomains;
final List<Map<String, int>>? topUpstreamResponses;
final List<Map<String, double>>? topUpstreamsAvgTime;
final List<int> dnsQueries;
final List<int> blockedFiltering;
final List<int> replacedSafebrowsing;
@ -25,6 +27,8 @@ class DnsStatistics {
required this.topQueriedDomains,
required this.topClients,
required this.topBlockedDomains,
required this.topUpstreamResponses,
required this.topUpstreamsAvgTime,
required this.dnsQueries,
required this.blockedFiltering,
required this.replacedSafebrowsing,
@ -42,6 +46,8 @@ class DnsStatistics {
topQueriedDomains: List<Map<String, int>>.from(json["top_queried_domains"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))),
topClients: List<Map<String, int>>.from(json["top_clients"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))),
topBlockedDomains: List<Map<String, int>>.from(json["top_blocked_domains"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))),
topUpstreamResponses: json["top_upstreams_responses"] != null ? List<Map<String, int>>.from(json["top_upstreams_responses"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))) : null,
topUpstreamsAvgTime: json["top_upstreams_avg_time"] != null ? List<Map<String, double>>.from(json["top_upstreams_avg_time"].map((x) => Map.from(x).map((k, v) => MapEntry<String, double>(k, v)))) : null,
dnsQueries: List<int>.from(json["dns_queries"].map((x) => x)),
blockedFiltering: List<int>.from(json["blocked_filtering"].map((x) => x)),
replacedSafebrowsing: List<int>.from(json["replaced_safebrowsing"].map((x) => x)),
@ -59,6 +65,8 @@ class DnsStatistics {
"top_queried_domains": List<dynamic>.from(topQueriedDomains.map((x) => Map.from(x).map((k, v) => MapEntry<String, dynamic>(k, v)))),
"top_clients": List<dynamic>.from(topClients.map((x) => Map.from(x).map((k, v) => MapEntry<String, dynamic>(k, v)))),
"top_blocked_domains": List<dynamic>.from(topBlockedDomains.map((x) => Map.from(x).map((k, v) => MapEntry<String, dynamic>(k, v)))),
"top_upstreams_responses": topUpstreamResponses != null ? List<dynamic>.from(topUpstreamResponses!.map((x) => Map.from(x).map((k, v) => MapEntry<String, dynamic>(k, v)))) : null,
"top_upstreams_avg_time": topUpstreamsAvgTime != null ? List<dynamic>.from(topUpstreamsAvgTime!.map((x) => Map.from(x).map((k, v) => MapEntry<String, dynamic>(k, v)))) : null,
"dns_queries": List<dynamic>.from(dnsQueries.map((x) => x)),
"blocked_filtering": List<dynamic>.from(blockedFiltering.map((x) => x)),
"replaced_safebrowsing": List<dynamic>.from(replacedSafebrowsing.map((x) => x)),

View file

@ -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<String, dynamic> json) => EncyptionValidation(
factory EncryptionValidation.fromJson(Map<String, dynamic> json) => EncryptionValidation(
subject: json["subject"],
issuer: json["issuer"],
keyType: json["key_type"],

View file

@ -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({

View file

@ -449,7 +449,7 @@ class AppConfigProvider with ChangeNotifier {
_showTopItemsChart = dbData['showTopItemsChart'];
if (dbData['homeTopItemsOrder'] != null) {
try {
_homeTopItemsOrder = List<HomeTopItems>.from(
final itemsOrder = List<HomeTopItems>.from(
List<String>.from(jsonDecode(dbData['homeTopItemsOrder'])).map((e) {
switch (e) {
case 'queriedDomains':
@ -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;

View file

@ -237,7 +237,7 @@ class StatusProvider with ChangeNotifier {
}
Future<bool> getServerStatus({
bool? withLoadingIndicator,
bool? withLoadingIndicator = true,
bool? overrideCheckServerVersion
}) async {
if (withLoadingIndicator == true) {

View file

@ -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';
@ -143,16 +142,11 @@ class _AddedListState extends State<AddedList> {
)
);
}
void openOptionsModal(Client client) {
showModal(
context: context,
builder: (ctx) => OptionsModal(
onDelete: () => openDeleteModal(client),
onEdit: () => openClientModal(client),
)
);
}
final clientsDisplay = clientsProvider.searchTermClients != null && clientsProvider.searchTermClients != ""
? widget.data.where(
(c) => c.name.toLowerCase().contains(clientsProvider.searchTermClients.toString()) || c.ids.where((id) => id.contains(clientsProvider.searchTermClients.toString())).isNotEmpty
).toList()
: widget.data;
return CustomTabContentList(
listPadding: widget.splitView == true
@ -177,12 +171,11 @@ class _AddedListState extends State<AddedList> {
],
),
),
itemsCount: widget.data.length,
itemsCount: clientsDisplay.length,
contentWidget: (index) => AddedClientTile(
selectedClient: widget.selectedClient,
client: widget.data[index],
client: clientsDisplay[index],
onTap: widget.onClientSelected,
onLongPress: openOptionsModal,
onEdit: statusProvider.serverStatus != null
? (c) => openClientModal(c)
: null,

View file

@ -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,
),
);
}

View file

@ -1,18 +1,18 @@
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;
final void Function(Client)? onEdit;
final void Function(Client) onDelete;
final Client? selectedClient;
@ -22,67 +22,54 @@ class AddedClientTile extends StatelessWidget {
super.key,
required this.client,
required this.onTap,
required this.onLongPress,
this.onEdit,
required this.onDelete,
this.selectedClient,
required this.splitView,
});
@override
State<AddedClientTile> createState() => _AddedClientTileState();
}
class _AddedClientTileState extends State<AddedClientTile> {
bool _isHover = false;
@override
Widget build(BuildContext context) {
final appConfigProvider = Provider.of<AppConfigProvider>(context);
if (splitView == true) {
if (widget.splitView == true) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(28),
child: ContextMenuArea(
builder: (context) => [
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 +81,7 @@ class AddedClientTile extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
@ -107,7 +94,7 @@ class AddedClientTile extends StatelessWidget {
Icon(
Icons.filter_list_rounded,
size: 19,
color: client.filteringEnabled == true
color: widget.client.filteringEnabled == true
? appConfigProvider.useThemeColorForStatus == true
? Theme.of(context).colorScheme.primary
: Colors.green
@ -119,7 +106,7 @@ class AddedClientTile extends StatelessWidget {
Icon(
Icons.vpn_lock_rounded,
size: 18,
color: client.safebrowsingEnabled == true
color: widget.client.safebrowsingEnabled == true
? appConfigProvider.useThemeColorForStatus == true
? Theme.of(context).colorScheme.primary
: Colors.green
@ -131,7 +118,7 @@ class AddedClientTile extends StatelessWidget {
Icon(
Icons.block,
size: 18,
color: client.parentalEnabled == true
color: widget.client.parentalEnabled == true
? appConfigProvider.useThemeColorForStatus == true
? Theme.of(context).colorScheme.primary
: Colors.green
@ -143,7 +130,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 +146,14 @@ class AddedClientTile extends StatelessWidget {
],
),
),
if (widget.onEdit != null && _isHover == true) ...[
const SizedBox(width: 8),
IconButton(
onPressed: () => widget.onEdit!(widget.client),
icon: const Icon(Icons.file_open_rounded),
tooltip: AppLocalizations.of(context)!.seeDetails,
)
]
],
)
),
@ -168,37 +163,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 +197,7 @@ class AddedClientTile extends StatelessWidget {
Icon(
Icons.filter_list_rounded,
size: 19,
color: client.filteringEnabled == true
color: widget.client.filteringEnabled == true
? appConfigProvider.useThemeColorForStatus == true
? Theme.of(context).colorScheme.primary
: Colors.green
@ -221,7 +209,7 @@ class AddedClientTile extends StatelessWidget {
Icon(
Icons.vpn_lock_rounded,
size: 18,
color: client.safebrowsingEnabled == true
color: widget.client.safebrowsingEnabled == true
? appConfigProvider.useThemeColorForStatus == true
? Theme.of(context).colorScheme.primary
: Colors.green
@ -233,7 +221,7 @@ class AddedClientTile extends StatelessWidget {
Icon(
Icons.block,
size: 18,
color: client.parentalEnabled == true
color: widget.client.parentalEnabled == true
? appConfigProvider.useThemeColorForStatus == true
? Theme.of(context).colorScheme.primary
: Colors.green
@ -245,7 +233,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

View file

@ -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<Clients> createState() => _ClientsState();
@ -20,36 +20,38 @@ class _ClientsState extends State<Clients> 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,
);
}
},
),
);
}
}

View file

@ -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) {

View file

@ -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<ClientsLists> createState() => _ClientsListsState();

View file

@ -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<StatusProvider>(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)
)
],
);
}
}

View file

@ -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<SearchClients> {
);
}
void openOptionsModal(Client client) {
showModal(
context: context,
builder: (ctx) => OptionsModal(
onDelete: () => openDeleteModal(client),
onEdit: () => openClientModal(client),
)
);
}
// void openOptionsModal(Client client) {
// showModal(
// context: context,
// builder: (ctx) => OptionsModal(
// onDelete: () => openDeleteModal(client),
// onEdit: () => openClientModal(client),
// )
// );
// }
return Scaffold(
appBar: AppBar(
@ -224,82 +225,96 @@ class _SearchClientsState extends State<SearchClients> {
primary: false,
itemCount: clientsScreen.length,
padding: const EdgeInsets.only(bottom: 0),
itemBuilder: (context, index) => ListTile(
contentPadding: index == 0
? const EdgeInsets.only(left: 20, right: 20, bottom: 15)
: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
isThreeLine: true,
onLongPress: () => openOptionsModal(clientsScreen[index]),
onTap: 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
)
],
)
],
),
),
)
)

View file

@ -172,7 +172,7 @@ class _FiltersListState extends State<FiltersList> {
loadStatus: widget.loadStatus,
onRefresh: () async {
final result = await filteringProvider.fetchFilters();
if (result == false) {
if (result == false && mounted) {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.errorLoadFilters,

View file

@ -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<Widget> actions;
const FiltersTripleColumn({
Key? key,
super.key,
required this.onRemoveCustomRule,
required this.onOpenDetailsModal,
required this.actions,
}) : super(key: key);
});
@override
Widget build(BuildContext context) {
@ -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,
),
),
),
),

View file

@ -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
)
),
),
);

View file

@ -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<Home> createState() => _HomeState();
@ -32,8 +32,11 @@ class _HomeState extends State<Home> {
late bool isVisible;
@override
initState(){
Provider.of<StatusProvider>(context, listen: false).getServerStatus();
initState() {
final statusProvider = Provider.of<StatusProvider>(context, listen: false);
statusProvider.getServerStatus(
withLoadingIndicator: statusProvider.serverStatus != null ? false : true
);
super.initState();
@ -233,75 +236,4 @@ class _HomeState extends State<Home> {
),
);
}
}
class TopItemsLists extends StatelessWidget {
final List<HomeTopItems> order;
const TopItemsLists({
Key? key,
required this.order,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final statusProvider = Provider.of<StatusProvider>(context);
List<Widget> 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(),
);
}
}

View file

@ -2,30 +2,35 @@ 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/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/models/menu_option.dart';
import 'package:adguard_home_manager/constants/enums.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;
final List<MenuOption> options;
final void Function(dynamic)? onTapEntry;
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);
required this.options,
this.onTapEntry,
this.unit,
});
@override
State<RowItem> createState() => _RowItemState();
@ -77,8 +82,6 @@ class _RowItemState extends State<RowItem> with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
final statusProvider = Provider.of<StatusProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context);
final logsProvider = Provider.of<LogsProvider>(context);
String? name;
if (widget.clients == true) {
@ -91,36 +94,10 @@ class _RowItemState extends State<RowItem> with TickerProviderStateMixin {
return Material(
color: Colors.transparent,
child: DomainOptions(
item: widget.domain,
isClient: widget.type == 'topClients',
isBlocked: widget.type == 'topBlockedDomains',
onTap: () {
if (widget.type == 'topQueriedDomains' || widget.type == 'topBlockedDomains') {
logsProvider.setSearchText(widget.domain);
logsProvider.setSelectedClients(null);
logsProvider.setAppliedFilters(
AppliedFiters(
selectedResultStatus: 'all',
searchText: widget.domain,
clients: null
)
);
appConfigProvider.setSelectedScreen(2);
}
else if (widget.type == 'topClients') {
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,
@ -195,10 +172,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<OthersRowItem> createState() => _OthersRowItemState();

View file

@ -7,26 +7,35 @@ 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/widgets/custom_pie_chart.dart';
import 'package:adguard_home_manager/providers/status_provider.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 {
final String type;
final HomeTopItems type;
final String label;
final List<Map<String, dynamic>> data;
final bool? clients;
final bool withChart;
final bool withProgressBar;
final String Function(dynamic) buildValue;
final List<MenuOption> menuOptions;
final void Function(dynamic)? onTapEntry;
const TopItems({
Key? key,
super.key,
required this.type,
required this.label,
required this.data,
this.clients
}) : super(key: key);
required this.withChart,
required this.withProgressBar,
required this.buildValue,
required this.menuOptions,
this.onTapEntry,
});
@override
State<TopItems> createState() => _TopItemsState();
@ -52,25 +61,9 @@ class _TopItemsState extends State<TopItems> {
@override
Widget build(BuildContext context) {
final statusProvider = Provider.of<StatusProvider>(context);
final width = MediaQuery.of(context).size.width;
List<Map<String, dynamic>> 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 [];
}
}
final withChart = widget.type != HomeTopItems.avgUpstreamResponseTime;
Map<String, double> chartData() {
Map<String, double> values = {};
@ -110,105 +103,183 @@ class _TopItemsState extends State<TopItems> {
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 (widget.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: widget.data,
clients: widget.type == HomeTopItems.recurrentClients,
type: widget.type,
showChart: withChart == true ? _showChart : false,
buildValue: widget.buildValue,
menuOptions: widget.menuOptions,
onTapEntry: widget.onTapEntry,
),
if (withChart == true) OthersRowItem(
items: widget.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 (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 (widget.data.length > 5) ...[
Padding(
@ -225,8 +296,12 @@ class _TopItemsState extends State<TopItems> {
builder: (context) => TopItemsModal(
type: widget.type,
title: widget.label,
isClient: widget.clients,
data: generateData(),
isClient: widget.type == HomeTopItems.recurrentClients,
data: widget.data,
withProgressBar: widget.withProgressBar,
buildValue: widget.buildValue,
options: widget.menuOptions,
onTapEntry: widget.onTapEntry,
)
)
}
@ -236,8 +311,12 @@ class _TopItemsState extends State<TopItems> {
builder: (context) => TopItemsScreen(
type: widget.type,
title: widget.label,
isClient: widget.clients,
data: generateData(),
isClient: widget.type == HomeTopItems.recurrentClients,
data: widget.data,
withProgressBar: widget.withProgressBar,
buildValue: widget.buildValue,
menuOptions: widget.menuOptions,
onTapEntry: widget.onTapEntry,
)
)
)
@ -266,21 +345,26 @@ class _TopItemsState extends State<TopItems> {
}
}
class ItemsList extends StatelessWidget {
class _ItemsList extends StatelessWidget {
final List<Color> colors;
final List<Map<String, dynamic>> data;
final bool? clients;
final String type;
final HomeTopItems type;
final bool showChart;
final String Function(dynamic) buildValue;
final List<MenuOption> menuOptions;
final void Function(dynamic)? onTapEntry;
const ItemsList({
Key? key,
const _ItemsList({
required this.colors,
required this.data,
required this.clients,
required this.type,
required this.showChart,
}) : super(key: key);
required this.buildValue,
required this.menuOptions,
this.onTapEntry,
});
@override
Widget build(BuildContext context) {
@ -290,10 +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].toString(),
number: buildValue(e.value.values.toList()[0]),
type: type,
chartColor: colors[e.key],
showColor: showChart,
options: menuOptions,
onTapEntry: onTapEntry,
)).toList()
);
}

View file

@ -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<HomeTopItems> order;
const TopItemsLists({
super.key,
required this.order,
});
@override
Widget build(BuildContext context) {
final statusProvider = Provider.of<StatusProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context);
final logsProvider = Provider.of<LogsProvider>(context);
List<Widget> 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(),
);
}
}

View file

@ -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<AppConfigProvider>(context);
final statusProvider = Provider.of<StatusProvider>(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),

View file

@ -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);

View file

@ -72,7 +72,8 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
String? validDataError;
int certKeyValidApi = 0;
EncyptionValidation? certKeyValid;
EncryptionValidation? certKeyValid;
String? encryptionResultMessage;
bool formEdited = false;
@ -140,20 +141,27 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
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<EncryptionSettings> {
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),

View file

@ -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<ReorderableTopItemsHome> createState() => _ReorderableTopItemsHomeState();
@ -38,10 +38,10 @@ class ReorderableTopItemsHome extends StatefulWidget {
class _ReorderableTopItemsHomeState extends State<ReorderableTopItemsHome> {
List<HomeTopItems> homeTopItemsList = [];
List<HomeTopItems> persistHomeTopItemsList = [];
List<ItemData> 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<ReorderableTopItemsHome> {
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<ReorderableTopItemsHome> {
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<bool> onWillPopScope() async {
Future<bool> 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<ReorderableTopItemsHome> {
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<ReorderableTopItemsHome> {
}
}
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<ReorderableTopItemsHome> {
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<ReorderableTopItemsHome> {
}
}
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) {

View file

@ -7,29 +7,37 @@ 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/applied_filters.dart';
import 'package:adguard_home_manager/providers/logs_provider.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 String type;
final HomeTopItems type;
final String title;
final bool? isClient;
final List<Map<String, dynamic>> data;
final bool withProgressBar;
final String Function(dynamic) buildValue;
final List<MenuOption> menuOptions;
final void Function(dynamic)? onTapEntry;
const TopItemsScreen({
Key? key,
super.key,
required this.type,
required this.title,
this.isClient,
required this.data,
}) : super(key: key);
required this.withProgressBar,
required this.buildValue,
required this.menuOptions,
this.onTapEntry,
});
@override
State<TopItemsScreen> createState() => _TopItemsScreenState();
@ -58,11 +66,10 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
Widget build(BuildContext context) {
final statusProvider = Provider.of<StatusProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context);
final logsProvider = Provider.of<LogsProvider>(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(
@ -152,42 +159,19 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
}
}
return DomainOptions(
item: screenData[index].keys.toList()[0],
isBlocked: widget.type == 'topBlockedDomains',
isClient: widget.type == 'topClients',
onTap: () {
if (widget.type == 'topQueriedDomains' || widget.type == 'topBlockedDomains') {
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 == 'topClients') {
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].toString(),
widget.buildValue(screenData[index].values.toList()[0]),
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant
),
@ -205,7 +189,7 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
),
const SizedBox(height: 5),
],
Row(
if (widget.withProgressBar == true) Row(
children: [
SizedBox(
width: 50,

View file

@ -7,28 +7,35 @@ 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/applied_filters.dart';
import 'package:adguard_home_manager/providers/logs_provider.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';
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<Map<String, dynamic>> data;
final bool withProgressBar;
final String Function(dynamic) buildValue;
final List<MenuOption> options;
final void Function(dynamic)? onTapEntry;
const TopItemsModal({
Key? key,
super.key,
required this.type,
required this.title,
this.isClient,
required this.data,
}) : super(key: key);
required this.withProgressBar,
required this.buildValue,
required this.options,
this.onTapEntry,
});
@override
State<TopItemsModal> createState() => _TopItemsModalState();
@ -56,12 +63,10 @@ class _TopItemsModalState extends State<TopItemsModal> {
@override
Widget build(BuildContext context) {
final statusProvider = Provider.of<StatusProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context);
final logsProvider = Provider.of<LogsProvider>(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(
@ -128,42 +133,19 @@ class _TopItemsModalState extends State<TopItemsModal> {
}
}
return DomainOptions(
isBlocked: widget.type == 'topBlockedDomains',
isClient: widget.type == 'topClients',
item: screenData[index].keys.toList()[0],
onTap: () {
if (widget.type == 'topQueriedDomains' || widget.type == 'topBlockedDomains') {
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 == 'topClients') {
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].toString(),
widget.buildValue(screenData[index].values.toList()[0]),
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant
),
@ -181,7 +163,7 @@ class _TopItemsModalState extends State<TopItemsModal> {
),
const SizedBox(height: 5),
],
Row(
if (widget.withProgressBar == true) Row(
children: [
SizedBox(
width: 50,

View file

@ -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);

View file

@ -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) {

View file

@ -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? isClient;
final String? item;
final Widget child;
final void Function() onTap;
final BorderRadius? borderRadius;
const DomainOptions({
Key? key,
required this.isBlocked,
this.isClient,
required this.item,
required this.child,
required this.onTap,
this.borderRadius
}) : super(key: key);
@override
Widget build(BuildContext context) {
final statusProvider = Provider.of<StatusProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(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<MenuOption> generateOptions() {
return [
if (isClient != true && isBlocked == true) MenuOption(
title: AppLocalizations.of(context)!.unblock,
icon: Icons.check,
action: () => blockUnblock(item!, 'unblock')
),
if (isClient != 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;
}
}
}

View file

@ -1,15 +1,75 @@
import 'package:adguard_home_manager/models/menu_option.dart';
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/custom_list_tile_dialog.dart';
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
class OptionsModal extends StatelessWidget {
import 'package:adguard_home_manager/models/menu_option.dart';
class OptionsMenu extends StatelessWidget {
final Widget child;
final List<MenuOption> options;
final dynamic value;
final BorderRadius? borderRadius;
final void Function(dynamic)? onTap;
const OptionsModal({
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,
),
),
);
}
}
class _OptionsModal extends StatelessWidget {
final List<MenuOption> options;
final dynamic value;
const _OptionsModal({
required this.options,
this.value,
});
@override
@ -43,7 +103,7 @@ class OptionsModal extends StatelessWidget {
icon: opt.icon,
onTap: () {
Navigator.pop(context);
opt.action();
opt.action(value);
},
)).toList()
),

View file

@ -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"

View file

@ -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.2+110
environment:
sdk: '>=2.18.1 <3.0.0'
@ -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