mirror of
https://github.com/JGeek00/adguard-home-manager.git
synced 2025-04-19 13:29:12 +00:00
Merge branch 'beta'
This commit is contained in:
commit
dab9f69e69
40 changed files with 2681 additions and 1777 deletions
|
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
||||
|
|
|
@ -19,7 +19,7 @@ pluginManagement {
|
|||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "7.2.2" apply false
|
||||
id "com.android.application" version '7.4.2' apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.20" apply false
|
||||
}
|
||||
|
||||
|
|
|
@ -758,5 +758,21 @@
|
|||
"invalidTime": "Invalid time",
|
||||
"removeDomain": "Remove domain",
|
||||
"addDomain": "Add domain",
|
||||
"notLess1Hour": "Time cannot be less than 1 hour"
|
||||
"notLess1Hour": "Time cannot be less than 1 hour",
|
||||
"rateLimit": "Rate limit",
|
||||
"subnetPrefixLengthIpv4": "Subnet prefix length for IPv4",
|
||||
"subnetPrefixLengthIpv6": "Subnet prefix length for IPv6",
|
||||
"rateLimitAllowlist": "Rate limit allowlist",
|
||||
"rateLimitAllowlistDescription": "IP addresses excluded from rate limiting",
|
||||
"dnsOptions": "DNS options",
|
||||
"editor": "Editor",
|
||||
"editCustomRules": "Edit custom rules",
|
||||
"savingCustomRules": "Saving custom rules...",
|
||||
"customRulesUpdatedSuccessfully": "Custom rules updated successfully",
|
||||
"customRulesNotUpdated": "Custom rules could not be updated",
|
||||
"reorder": "Reorder",
|
||||
"showHide": "Show/hide",
|
||||
"noElementsReorderMessage": "Enable some elements on the show/hide tab to reorder them here.",
|
||||
"enablePlainDns": "Enable plain DNS",
|
||||
"enablePlainDnsDescription": "Plain DNS is enabled by default. You can disable it to force all devices to use encrypted DNS. To do this, you must enable at least one encrypted DNS protocol."
|
||||
}
|
|
@ -758,5 +758,21 @@
|
|||
"invalidTime": "Tiempo no válido",
|
||||
"removeDomain": "Eliminar dominio",
|
||||
"addDomain": "Añadir dominio",
|
||||
"notLess1Hour": "El tiempo no puede ser inferior a 1 hora"
|
||||
"notLess1Hour": "El tiempo no puede ser inferior a 1 hora",
|
||||
"rateLimit": "Limitación de velocidad",
|
||||
"subnetPrefixLengthIpv4": "Longitud del prefijo de subred para IPv4",
|
||||
"subnetPrefixLengthIpv6": "Longitud del prefijo de subred para IPv6",
|
||||
"rateLimitAllowlist": "Lista de permitidos de limitación de velocidad",
|
||||
"rateLimitAllowlistDescription": "Direcciones IP excluidas de la limitación de velocidad",
|
||||
"dnsOptions": "Opciones de DNS",
|
||||
"editor": "Editor",
|
||||
"editCustomRules": "Editar reglas personalizadas",
|
||||
"savingCustomRules": "Guardando reglas personalizadas...",
|
||||
"customRulesUpdatedSuccessfully": "Reglas personalizadas actualizadas correctamente",
|
||||
"customRulesNotUpdated": "Las reglas personalizadas no pudieron ser actualizadas",
|
||||
"reorder": "Reordenar",
|
||||
"showHide": "Mostrar/ocultar",
|
||||
"noElementsReorderMessage": "Activa algunos elementos en la pestaña de mostrar/ocultar para reordenarlos aquí.",
|
||||
"enablePlainDns": "Activar DNS simple (sin cifrado)",
|
||||
"enablePlainDnsDescription": "El DNS simple (sin cifrado) está activado de forma predeterminada. Puedes desactivarlo para obligar a todos los dispositivos a utilizar DNS cifrado. Para ello, debes habilitar al menos un protocolo DNS cifrado."
|
||||
}
|
|
@ -11,7 +11,7 @@
|
|||
"port": "Bağlantı noktası",
|
||||
"username": "Kullanıcı adı",
|
||||
"password": "Şifre",
|
||||
"defaultServer": "Varsayılan sunucu",
|
||||
"defaultServer": "Varsayılan sunucu olarak ayarla",
|
||||
"general": "Genel",
|
||||
"connection": "Bağlantı",
|
||||
"authentication": "Kimlik doğrulama",
|
||||
|
@ -40,18 +40,18 @@
|
|||
"removeWarning": "Bu AdGuard Home sunucusuyla olan bağlantıyı kaldırmak istediğinizden emin misiniz?",
|
||||
"cancel": "İptal",
|
||||
"defaultConnection": "Varsayılan bağlantı",
|
||||
"setDefault": "Varsayılan ayarla",
|
||||
"setDefault": "Varsayılan sunucu yap",
|
||||
"edit": "Düzenle",
|
||||
"delete": "Sil",
|
||||
"save": "Kaydet",
|
||||
"serverStatus": "Sunucu durumu",
|
||||
"connectionNotUpdated": "Bağlantı Güncellenmedi",
|
||||
"ruleFilteringWidget": "Kural filtreleme",
|
||||
"safeBrowsingWidget": "Güvenli gezinti",
|
||||
"safeBrowsingWidget": "Güvenli gezinme",
|
||||
"parentalFilteringWidget": "Ebeveyn filtreleme",
|
||||
"safeSearchWidget": "Güvenli arama",
|
||||
"ruleFiltering": "Kural filtreleme",
|
||||
"safeBrowsing": "Güvenli gezinti",
|
||||
"safeBrowsing": "Güvenli gezinme",
|
||||
"parentalFiltering": "Ebeveyn filtreleme",
|
||||
"safeSearch": "Güvenli arama",
|
||||
"serverStatusNotRefreshed": "Sunucu durumu yenilenemedi",
|
||||
|
@ -63,9 +63,9 @@
|
|||
"topBlockedDomains": "En çok engellenenler",
|
||||
"appSettings": "Uygulama ayarları",
|
||||
"theme": "Tema",
|
||||
"light": "Aydınlık",
|
||||
"dark": "Karanlık",
|
||||
"systemDefined": "Sistemle uyumlu hale getir",
|
||||
"light": "Açık",
|
||||
"dark": "Koyu",
|
||||
"systemDefined": "Otomatik (Cihazınızın renk düzenine göre)",
|
||||
"close": "Kapat",
|
||||
"connectedTo": "Bağlandı:",
|
||||
"selectedServer": "Seçili sunucu:",
|
||||
|
@ -80,8 +80,8 @@
|
|||
"appVersion": "Uygulama sürümü",
|
||||
"createdBy": "Geliştirici",
|
||||
"clients": "İstemciler",
|
||||
"allowed": "İzin verildi",
|
||||
"blocked": "Engellendi",
|
||||
"allowed": "İzin verilen",
|
||||
"blocked": "Engellenen",
|
||||
"noClientsList": "Bu listede hiç istemci yok",
|
||||
"activeClients": "Etkin",
|
||||
"removeClient": "İstemciyi kaldır",
|
||||
|
@ -102,7 +102,7 @@
|
|||
"copyLogsClipboard": "Günlükleri panoya kopyala",
|
||||
"logsCopiedClipboard": "Günlükler panoya kopyalandı",
|
||||
"advancedSettings": "Gelişmiş ayarlar",
|
||||
"dontCheckCertificate": "SSL sertifikasını asla kontrol etme",
|
||||
"dontCheckCertificate": "SSL sertifikasını kontrol etme",
|
||||
"dontCheckCertificateDescription": "Sunucunun SSL sertifikası doğrulamasını geçersiz kılar",
|
||||
"advancedSetupDescription": "Gelişmiş seçenekleri yönet",
|
||||
"settingsUpdatedSuccessfully": "Ayarlar başarıyla güncellendi.",
|
||||
|
@ -114,8 +114,8 @@
|
|||
"processedRow": "İşlendi (Liste yok)",
|
||||
"blockedBlacklist": "Engellendi\nKara Liste",
|
||||
"blockedBlacklistRow": "Engellendi (Kara liste)",
|
||||
"blockedSafeBrowsing": "Engellendi\nGüvenli gezinti",
|
||||
"blockedSafeBrowsingRow": "Engellendi (Güvenli gezinti)",
|
||||
"blockedSafeBrowsing": "Engellendi\nGüvenli gezinme",
|
||||
"blockedSafeBrowsingRow": "Engellendi (Güvenli gezinme)",
|
||||
"blockedParental": "Engellendi\nEbeveyn filtreleme",
|
||||
"blockedParentalRow": "Engellendi (Ebeveyn filtreleme)",
|
||||
"blockedInvalid": "Engellendi\nGeçersiz",
|
||||
|
@ -181,7 +181,7 @@
|
|||
"visitGooglePlay": "Google Play sayfasını ziyaret et",
|
||||
"gitHub": "Kaynak kodlarına GitHub'dan ulaşabilirsiniz",
|
||||
"blockClient": "İstemciyi engelle",
|
||||
"selectTags": "Etiketleri seçin",
|
||||
"selectTags": "Etiketleri seç",
|
||||
"noTagsSelected": "Seçili etiket yok",
|
||||
"tags": "Etiketler",
|
||||
"identifiers": "Tanımlayıcılar",
|
||||
|
@ -190,19 +190,19 @@
|
|||
"noIdentifiers": "Tanımlayıcı eklenmedi",
|
||||
"useGlobalSettings": "Küresel ayarları kullan",
|
||||
"enableFiltering": "Filtrelemeyi etkinleştir",
|
||||
"enableSafeBrowsing": "Güvenli gezintiyi etkinleştir",
|
||||
"enableSafeBrowsing": "Güvenli gezinmeyi etkinleştir",
|
||||
"enableParentalControl": "Ebeveyn kontrolünü etkinleştir",
|
||||
"enableSafeSearch": "Güvenli aramayı aktif et",
|
||||
"enableSafeSearch": "Güvenli aramayı etkinleştir",
|
||||
"blockedServices": "Engellenen hizmetler",
|
||||
"selectBlockedServices": "Engellenen hizmetleri seç",
|
||||
"noBlockedServicesSelected": "Engellenen hizmetler seçilmedi",
|
||||
"services": "Hizmetler",
|
||||
"servicesBlocked": "Hizmetler engellendi",
|
||||
"tagsSelected": "Seçilen etiketler",
|
||||
"upstreamServers": "Üst kaynak sunucuları",
|
||||
"tagsSelected": "Etiket seçildi",
|
||||
"upstreamServers": "Üst sunucular",
|
||||
"serverAddress": "Sunucu adresi",
|
||||
"noUpstreamServers": "Üst kaynak sunucusu yok.",
|
||||
"willBeUsedGeneralServers": "Genel üst kaynak sunucuları kullanılacak.",
|
||||
"noUpstreamServers": "Üst sunucu yok.",
|
||||
"willBeUsedGeneralServers": "Genel üst sunucular kullanılacak.",
|
||||
"added": "Eklenenler",
|
||||
"clientUpdatedSuccessfully": "İstemci başarıyla güncellendi",
|
||||
"clientNotUpdated": "İstemci güncellenemedi",
|
||||
|
@ -215,7 +215,7 @@
|
|||
"blacklists": "Kara listeler",
|
||||
"rules": "Kurallar",
|
||||
"customRules": "Özel kurallar",
|
||||
"enabledRules": "Etkin kurallar",
|
||||
"enabledRules": "Etkin kural",
|
||||
"enabled": "Etkin",
|
||||
"disabled": "Devre dışı",
|
||||
"rule": "Kural",
|
||||
|
@ -234,7 +234,7 @@
|
|||
"addWhitelist": "Beyaz liste ekle",
|
||||
"addBlacklist": "Kara liste ekle",
|
||||
"urlNotValid": "Bağlantı adresi geçerli değil",
|
||||
"urlAbsolutePath": "Bağlantı adresi veya kesin dosya yolu",
|
||||
"urlAbsolutePath": "Bağlantı adresi veya dosya yolu",
|
||||
"addingList": "Liste ekleniyor...",
|
||||
"listAdded": "Liste başarıyla eklendi. Eklenen öğeler:",
|
||||
"listAlreadyAdded": "Liste zaten eklenmiş",
|
||||
|
@ -267,13 +267,13 @@
|
|||
"seeDnsAddresses": "DNS adreslerine göz at",
|
||||
"dnsPort": "DNS bağlantı noktası",
|
||||
"httpPort": "HTTP bağlantı noktası",
|
||||
"protectionEnabled": "Koruma etkin mi?",
|
||||
"dhcpAvailable": "DHCP mevcut mu?",
|
||||
"serverRunning": "Sunucu çalışıyor mu?",
|
||||
"protectionEnabled": "Koruma durumu",
|
||||
"dhcpAvailable": "DHCP durumu",
|
||||
"serverRunning": "Sunucu durumu",
|
||||
"serverVersion": "Sunucu sürümü",
|
||||
"serverLanguage": "Sunucu dili",
|
||||
"yes": "Evet",
|
||||
"no": "Hayır",
|
||||
"yes": "Etkin",
|
||||
"no": "Mevcut değil",
|
||||
"allowedClients": "İzin verilen istemciler",
|
||||
"disallowedClients": "İzin verilmeyen istemciler",
|
||||
"disallowedDomains": "İzin verilmeyen alan adları",
|
||||
|
@ -288,7 +288,7 @@
|
|||
"addClientFieldDescription": "CIDR'ler, IP adresi veya ClientID",
|
||||
"clientIdentifier": "İstemci tanımlayıcısı",
|
||||
"allowClient": "İstemciye izin ver",
|
||||
"disallowClient": "İstemciye izin verme",
|
||||
"disallowClient": "İstemciyi engelle",
|
||||
"noDisallowedDomains": "İzin verilmeyen alan adı yok",
|
||||
"domainNotAdded": "Alan adı eklenemedi",
|
||||
"statusSelected": "Durum seçildi.",
|
||||
|
@ -305,11 +305,11 @@
|
|||
"block": "Engelle",
|
||||
"unblock": "Engeli kaldır",
|
||||
"custom": "Özel",
|
||||
"addImportant": "Ekle ($important)",
|
||||
"addImportant": "Başına $important ekle",
|
||||
"howCreateRules": "Özel kurallar nasıl oluşturulur?",
|
||||
"examples": "Örnekler",
|
||||
"example1": "example.org ve tüm alt alan adlarına erişimi engeller.",
|
||||
"example2": "example.org ve tüm alt alan adlarına erişimi engellemeyi kaldırır.",
|
||||
"example1": "example.org (ornek.org) ve tüm alt alan adlarına erişimi engeller.",
|
||||
"example2": "example.org (ornek.org) ve tüm alt alan adlarına erişimi engellemeyi kaldırır.",
|
||||
"example3": "Yorum ekler.",
|
||||
"example4": "Belirtilen düzenli ifadeye uyan alan adlarına erişimi engeller.",
|
||||
"moreInformation": "Daha fazla bilgi",
|
||||
|
@ -342,7 +342,7 @@
|
|||
"dhcpSettingsNotLoaded": "DHCP ayarları yüklenemedi",
|
||||
"loadingDhcp": "DHCP ayarları yükleniyor...",
|
||||
"enableDhcpServer": "DHCP sunucusunu etkinleştir",
|
||||
"selectInterface": "Arayüz seçin",
|
||||
"selectInterface": "Arayüz seç",
|
||||
"hardwareAddress": "Donanım adresi",
|
||||
"gatewayIp": "Ağ Geçidi IP'si",
|
||||
"ipv4addresses": "IPv4 adresleri",
|
||||
|
@ -368,37 +368,37 @@
|
|||
"restoringConfig": "Yapılandırma geri yükleniyor...",
|
||||
"configRestored": "Yapılandırma başarıyla sıfırlandı",
|
||||
"configNotRestored": "Yapılandırma sıfırlanamadı",
|
||||
"dhcpStatic": "DHCP statik kiralamaları",
|
||||
"noDhcpStaticLeases": "DHCP statik kiralamaları bulunamadı",
|
||||
"dhcpStatic": "DHCP statik kiraları",
|
||||
"noDhcpStaticLeases": "DHCP statik kirası bulunamadı",
|
||||
"deleting": "Siliniyor...",
|
||||
"staticLeaseDeleted": "DHCP statik kiralama başarıyla silindi",
|
||||
"staticLeaseNotDeleted": "DHCP statik kiralaması silinemedi",
|
||||
"deleteStaticLease": "Statik kiralamayı sil",
|
||||
"staticLeaseDeleted": "DHCP statik kirası başarıyla silindi",
|
||||
"staticLeaseNotDeleted": "DHCP statik kirası silinemedi",
|
||||
"deleteStaticLease": "Statik kirasını sil",
|
||||
"deleteStaticLeaseDescription": "DHCP statik kirası silinecek. Bu işlem geri alınamaz.",
|
||||
"addStaticLease": "Statik kiralama ekleyin",
|
||||
"addStaticLease": "Statik kira ekleyin",
|
||||
"macAddress": "MAC adresi",
|
||||
"macAddressNotValid": "MAC adresi geçersiz",
|
||||
"hostName": "Ana bilgisayar adı",
|
||||
"hostNameError": "Ana bilgisayar adı boş olamaz",
|
||||
"creating": "Oluşturuluyor...",
|
||||
"staticLeaseCreated": "DHCP statik kiralaması başarıyla oluşturuldu",
|
||||
"staticLeaseNotCreated": "DHCP statik kiralaması oluşturulamadı",
|
||||
"staticLeaseExists": "DHCP statik kiralaması zaten mevcut",
|
||||
"staticLeaseCreated": "DHCP statik kirası başarıyla oluşturuldu",
|
||||
"staticLeaseNotCreated": "DHCP statik kirası oluşturulamadı",
|
||||
"staticLeaseExists": "DHCP statik kirası zaten mevcut",
|
||||
"serverNotConfigured": "Sunucu yapılandırılmamış",
|
||||
"restoreLeases": "Kiralamaları sıfırla",
|
||||
"restoreLeasesMessage": "Devam etmek istediğinizden emin misiniz? Bu, mevcut tüm kiralamaları sıfırlayacaktır. Bu işlem geri alınamaz.",
|
||||
"restoringLeases": "Kiralamalar sıfırlanıyor...",
|
||||
"leasesRestored": "Kiralamalar başarıyla sıfırlandı",
|
||||
"restoreLeases": "Kiraları sıfırla",
|
||||
"restoreLeasesMessage": "Devam etmek istediğinizden emin misiniz? Bu, mevcut tüm kiraları sıfırlayacaktır. Bu işlem geri alınamaz.",
|
||||
"restoringLeases": "Kiralar sıfırlanıyor...",
|
||||
"leasesRestored": "Kiralar başarıyla sıfırlandı",
|
||||
"leasesNotRestored": "Kiralar sıfırlanamadı",
|
||||
"dhcpLeases": "DHCP kiralamaları",
|
||||
"noLeases": "Kullanılabilir DHCP kiralaması yok",
|
||||
"dhcpLeases": "DHCP kiraları",
|
||||
"noLeases": "Kullanılabilir DHCP kiraları yok",
|
||||
"dnsRewrites": "DNS yeniden yazımları",
|
||||
"dnsRewritesDescription": "Özel DNS kurallarını yapılandır",
|
||||
"loadingRewriteRules": "Yeniden yazım kuralları yükleniyor...",
|
||||
"rewriteRulesNotLoaded": "DNS yeniden yazım kuralları yüklenemedi.",
|
||||
"noRewriteRules": "DNS yeniden yazım kuralları yok",
|
||||
"answer": "Yanıt",
|
||||
"deleteDnsRewrite": "DNS yeniden yazımı sil",
|
||||
"deleteDnsRewrite": "DNS yeniden yazımını sil",
|
||||
"deleteDnsRewriteMessage": "Bu DNS yeniden yazımını silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
|
||||
"dnsRewriteRuleDeleted": "DNS yeniden yazım kuralı başarıyla silindi",
|
||||
"dnsRewriteRuleNotDeleted": "DNS yeniden yazım kuralı silinemedi",
|
||||
|
@ -409,7 +409,7 @@
|
|||
"logsSettings": "Günlük ayarları",
|
||||
"enableLog": "Günlüğü etkinleştir",
|
||||
"clearLogs": "Günlükleri temizle",
|
||||
"anonymizeClientIp": "İstemci IP'sini anonimleştir",
|
||||
"anonymizeClientIp": "İstemci IP'sini gizle",
|
||||
"hours6": "6 saat",
|
||||
"days30": "30 gün",
|
||||
"days90": "90 gün",
|
||||
|
@ -427,17 +427,17 @@
|
|||
"noItems": "Burada gösterilecek öğe yok",
|
||||
"dnsSettings": "DNS ayarları",
|
||||
"dnsSettingsDescription": "DNS sunucuları ile bağlantıyı yapılandır",
|
||||
"upstreamDns": "Üst kaynak DNS sunucuları",
|
||||
"upstreamDns": "Üst DNS sunucuları",
|
||||
"bootstrapDns": "Önyükleme DNS sunucuları",
|
||||
"noUpstreamDns": "Üst kaynak DNS sunucuları eklenmedi.",
|
||||
"noUpstreamDns": "Üst DNS sunucuları eklenmedi.",
|
||||
"dnsMode": "DNS modu",
|
||||
"noDnsMode": "DNS modu seçili değil",
|
||||
"loadBalancing": "Yük dengeleme",
|
||||
"parallelRequests": "Paralel istekler",
|
||||
"fastestIpAddress": "En hızlı IP adresi",
|
||||
"loadBalancingDescription": "Her seferinde bir üst kaynak sunucusuna sorgu yap. AdGuard Home, sunucuyu seçmek için ağırlıklı rastgele algoritmasını kullanır, böylece en hızlı sunucu daha sık kullanılır.",
|
||||
"parallelRequestsDescription": "Tüm üst kaynak sunucularını aynı anda sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanın.",
|
||||
"fastestIpAddressDescription": "Tüm DNS sunucularına sorgu yapın ve tüm yanıtlar arasında en hızlı IP adresini döndürün. Bu, AdGuard Home'un tüm DNS sunucularından yanıtları beklemesi gerektiği için DNS sorgularını yavaşlatır, ancak genel bağlantıyı iyileştirir.",
|
||||
"loadBalancingDescription": "Her seferinde bir üst sunucuya sorgu yapar. 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 sunucuları aynı anda sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanır.",
|
||||
"fastestIpAddressDescription": "Tüm DNS sunucularına sorgu yapın ve tüm yanıtlar arasında en hızlı IP adresini döndürür. Bu, AdGuard Home'un tüm DNS sunucularından yanıtları beklemesi gerektiği için DNS sorgularını yavaşlatır, ancak genel bağlantıyı iyileştirir.",
|
||||
"noBootstrapDns": "Önyükleme DNS sunucuları eklenmedi.",
|
||||
"bootstrapDnsServersInfo": "Önyükleme DNS sunucuları, üst kaynaklarda belirttiğiniz DoH/DoT çözümleyicilerinin IP adreslerini çözmek için kullanılır.",
|
||||
"privateReverseDnsServers": "Özel ters DNS sunucuları",
|
||||
|
@ -446,36 +446,36 @@
|
|||
"addItem": "Öğe ekle",
|
||||
"noServerAddressesAdded": "Sunucu adresleri eklenmedi.",
|
||||
"usePrivateReverseDnsResolvers": "Özel ters DNS çözümleyicilerini kullan",
|
||||
"usePrivateReverseDnsResolversDescription": "Bu üst kaynak sunucularını kullanarak yerel olarak sunulan adresler için ters DNS sorguları gerçekleştirin. Devre dışı bırakılırsa, AdGuard Home, DHCP, /etc/hosts vb. kaynaklardan bilinen istemciler dışında tüm PTR isteklerine NXDOMAIN yanıtı verir.",
|
||||
"usePrivateReverseDnsResolversDescription": "Bu üst sunucuları kullanarak yerel olarak sunulan adresler için ters DNS sorguları gerçekleştirin. Devre dışı bırakılırsa, AdGuard Home, DHCP, /etc/hosts vb. kaynaklardan bilinen istemciler dışında tüm PTR isteklerine NXDOMAIN yanıtı verir.",
|
||||
"enableReverseResolving": "İstemcilerin IP adreslerinin ters çözümlemesini etkinleştir",
|
||||
"enableReverseResolvingDescription": "İstemcilerin IP adreslerini karşılık gelen çözücülere PTR sorguları göndererek IP adreslerini tersine çözümleyerek (yerel istemciler için özel DNS sunucuları, genel IP adresine sahip istemciler için üst kaynak sunucuları) istemcilerin ana bilgisayar adlarını tersine çöz.",
|
||||
"enableReverseResolvingDescription": "Karşılık gelen çözümleyicilere (yerel istemciler için özel DNS sunucuları, genel IP adresleri olan istemciler için üst sunuculara) PTR sorguları göndererek istemcilerin IP adreslerini ana makine adlarının tersine çözer.",
|
||||
"dnsServerSettings": "AdGuard Home DNS sunucusu ayarları",
|
||||
"limitRequestsSecond": "Saniye başına sınırlama isteği",
|
||||
"limitRequestsSecond": "Sıklık limiti",
|
||||
"valueNotNumber": "Değer bir sayı değil",
|
||||
"enableEdns": "EDNS istemci alt ağını etkinleştir",
|
||||
"enableEdnsDescription": "Kaynak yönü isteklerine EDNS İstemci Alt Ağı Seçeneği (ECS) ekleyin ve istemciler tarafından gönderilen değerleri sorgu günlüğüne kaydedin.",
|
||||
"enableEdnsDescription": "Kaynak yönü isteklerine EDNS İstemci Alt Ağı Seçeneği (ECS) ekler ve istemciler tarafından gönderilen değerleri sorgu günlüğüne kaydeder.",
|
||||
"enableDnssec": "DNSSEC'i etkinleştir",
|
||||
"enableDnssecDescription": "Giden DNS sorguları için DNSSEC özelliğini etkinleştir ve sonucu kontrol edin.(DNSSEC etkinleştirilmiş bir çözümleyici gerekli)",
|
||||
"enableDnssecDescription": "Giden DNS sorguları için DNSSEC özelliğini etkinleştirir ve sonucu kontrol eder. (DNSSEC etkinleştirilmiş bir çözümleyici gerekli)",
|
||||
"disableResolvingIpv6": "IPv6 adreslerinin çözümlenmesini devre dışı bırak",
|
||||
"disableResolvingIpv6Description": "IPv6 adresleri için tüm DNS sorgularını bırakın (AAAA yazın) ve HTTPS yanıtlarından IPv6 ipuçlarını kaldırın.",
|
||||
"disableResolvingIpv6Description": "IPv6 adresleri için tüm DNS sorgularını bırakır (AAAA yazar) ve HTTPS yanıtlarından IPv6 ipuçlarını kaldırır.",
|
||||
"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 verir. (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 verir.",
|
||||
"refusedDescription": "REFUSED kodu ile yanıt verir.",
|
||||
"nxdomainDescription": "NXDOMAIN kodu ile yanıt verir.",
|
||||
"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 verir. (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 verir.",
|
||||
"dnsCacheConfig": "DNS önbellek yapılandırması",
|
||||
"cacheSize": "Önbellek boyutu",
|
||||
"inBytes": "Bayt olarak",
|
||||
"inBytes": "Alınacak önbelleğin boyutunu ayarla (bayt olarak)",
|
||||
"overrideMinimumTtl": "Minimum kullanım süresini geçersiz kıl",
|
||||
"overrideMinimumTtlDescription": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan minimum kullanım süresi değerini ayarlayın (saniye olarak)",
|
||||
"overrideMinimumTtlDescription": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan minimum kullanım süresi değerini (TTL) saniye olarak ayarlayın.",
|
||||
"overrideMaximumTtl": "Maksimum kullanım süresini geçersiz kıl",
|
||||
"overrideMaximumTtlDescription": "DNS önbelleğindeki girişler için maksimum kullanım süresi değerini ayarlayın (saniye olarak)",
|
||||
"overrideMaximumTtlDescription": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan maksimum kullanım süresi değerini (TTL) saniye olarak ayarlayın.",
|
||||
"optimisticCaching": "İyimser önbelleğe alma",
|
||||
"optimisticCachingDescription": "Girişlerin süresi dolmuş olsa bile Adguard Home'un önbellekten yanıt vermesini sağlayın ve aynı zamanda bunları yenilemeye çalışın.",
|
||||
"optimisticCachingDescription": "Girişlerin süresi dolmuş olsa bile Adguard Home'un önbellekten yanıt vermesini sağlar ve aynı zamanda bunları yenilemeye çalışır.",
|
||||
"loadingDnsConfig": "DNS yapılandırması yükleniyor...",
|
||||
"dnsConfigNotLoaded": "DNS yapılandırması yüklenemedi.",
|
||||
"blockingIpv4": "IPv4 engelleniyor",
|
||||
|
@ -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 kaynak sunucularını ve DNS modunu yapılandır",
|
||||
"upstreamDnsDescription": "Üst sunucuları 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",
|
||||
|
@ -513,13 +513,13 @@
|
|||
"certificatesDescription": "Şifreleme kullanmak için, alan adınız için geçerli bir SSL sertifikası zinciri sağlamanız gereklidir. letsencrypt.org'dan ücretsiz bir sertifika alabilir veya güvenilir sertifika yetkililerinden satın alabilirsiniz.",
|
||||
"certificateFilePath": "Sertifika dosyası belirle",
|
||||
"pasteCertificateContent": "Sertifika içeriğini yapıştır",
|
||||
"certificatePath": "Sertifika dosya yolu",
|
||||
"certificatePath": "Sertifikanın dosya yolu",
|
||||
"certificateContent": "Sertifika içeriği",
|
||||
"privateKey": "Özel anahtarlar",
|
||||
"privateKeyFile": "Özel anahtar dosyası belirle",
|
||||
"pastePrivateKey": "Özel anahtar içeriğini yapıştır",
|
||||
"usePreviousKey": "Önceden kaydedilmiş anahtarı kullan",
|
||||
"privateKeyPath": "Özel anahtar dosya yolu",
|
||||
"usePreviousKey": "Önceden kaydedilmiş olan anahtarı kullan",
|
||||
"privateKeyPath": "Özel anahtarın dosya yolu",
|
||||
"invalidCertificate": "Geçersiz sertifika",
|
||||
"invalidPrivateKey": "Geçersiz özel anahtar",
|
||||
"validatingData": "Veri doğrulama",
|
||||
|
@ -560,8 +560,8 @@
|
|||
"validPrivateKey": "Geçerli özel anahtar",
|
||||
"expirationDate": "Son kullanma tarihi",
|
||||
"keysNotMatch": "Geçersiz bir sertifika veya anahtar: tls: özel anahtar genel anahtarla eşleşmiyor.",
|
||||
"timeLogs": "Günlüklerdeki işlem süresi",
|
||||
"timeLogsDescription": "Günlükler listesinde zaman yerine işlem süresini göster",
|
||||
"timeLogs": "Günlüklerde işlem süresi göster",
|
||||
"timeLogsDescription": "Günlükler listesinde zaman yerine işlem süresini göster.",
|
||||
"hostNames": "Ana bilgisayar adları",
|
||||
"keyType": "Anahtar türü",
|
||||
"updateAvailable": "Güncelleme mevcut",
|
||||
|
@ -591,7 +591,7 @@
|
|||
"updates": "Güncellemeler",
|
||||
"updatesDescription": "AdGuard Home sunucusunu güncelle",
|
||||
"updateNow": "Şimdi güncelle",
|
||||
"currentVersion": "Mevcut sürüm",
|
||||
"currentVersion": "Yüklü sürüm",
|
||||
"requestStartUpdateFailed": "Güncellemeyi başlatma isteği başarısız oldu",
|
||||
"requestStartUpdateSuccessful": "Güncellemeyi başlatma isteği başarılı",
|
||||
"serverUpdated": "Sunucu güncellendi",
|
||||
|
@ -618,15 +618,15 @@
|
|||
"copiedClipboard": "Panoya kopyalandı",
|
||||
"seeDetails": "Detayları gör",
|
||||
"listNotAvailable": "Liste mevcut değil",
|
||||
"copyListUrl": "Liste bağlantısını kopyala",
|
||||
"listUrlCopied": "Panoya kopyalanan bağlantı adresini listeleyin",
|
||||
"copyListUrl": "Bağlantıyı kopyala",
|
||||
"listUrlCopied": "Liste bağlantısı panoya kopyalandı",
|
||||
"unsupportedVersion": "Desteklenmeyen sürüm",
|
||||
"unsupprtedVersionMessage": "Sunucu sürümünüz {version} için destek garantisi verilmiyor. Bu uygulamanın bu sunucu sürümüyle çalışmasında bazı sorunlar olabilir. AdGuard Home Yöneticisi, AdGuard Home sunucunun kararlı sürümleriyle çalışacak şekilde tasarlanmıştır. Alfa ve beta sürümleriyle çalışabilir, ancak uyumluluk garanti edilmez ve uygulama bu sürümlerle çalışırken bazı sorunlar yaşayabilir.",
|
||||
"iUnderstand": "Anladım",
|
||||
"appUpdates": "Uygulama güncellemeleri",
|
||||
"usingLatestVersion": "En son sürümü kullanıyorsunuz :)",
|
||||
"ipLogs": "Günlüklerdeki IP",
|
||||
"ipLogsDescription": "Günlükler listesinde istemci adı yerine IP adresini göster",
|
||||
"usingLatestVersion": "En son sürümü kullanıyorsunuz",
|
||||
"ipLogs": "Günlüklerde IP adresini göster",
|
||||
"ipLogsDescription": "Günlükler listesinde istemci adı yerine IP adresini göster.",
|
||||
"application": "Uygulama",
|
||||
"combinedChart": "Birleştirilmiş grafik",
|
||||
"combinedChartDescription": "Tüm grafikleri bir araya getirir.",
|
||||
|
@ -655,11 +655,11 @@
|
|||
"queries": "Sorgular",
|
||||
"adultSites": "Yetişkin içerikler",
|
||||
"quickFilters": "Hızlı filtreler",
|
||||
"searchDomainInternet": "İnternette alan adı ara",
|
||||
"searchDomainInternet": "Alan adını arat",
|
||||
"hideServerAddress": "Sunucu adresini gizle",
|
||||
"hideServerAddressDescription": "Ana ekranda sunucu adresini gizler.",
|
||||
"topItemsOrder": "Öne çıkan öğeler sıralaması",
|
||||
"topItemsOrderDescription": "Ana ekrandaki öne çıkan öğe listelerini sırala",
|
||||
"topItemsOrderDescription": "Ana ekrandaki öne çıkan öğe listelerini sıralayın.",
|
||||
"topItemsReorderInfo": "Yeniden sıralamak için bir öğeyi basılı tutun ve kaydırın.",
|
||||
"discardChanges": "Değişiklikleri iptal et",
|
||||
"discardChangesDescription": "Değişiklikleri iptal etmek istediğinizden emin misiniz?",
|
||||
|
@ -687,6 +687,92 @@
|
|||
"unsupportedServerVersionMessage": "AdGuard Home sunucu sürümünüz çok eski ve AdGuard Home Manager tarafından desteklenmiyor. Bu uygulamayı kullanmak için AdGuard Home sunucunuzu daha yeni bir sürüme yükseltmeniz gerekecektir.",
|
||||
"yourVersion": "Yüklü sürüm: {version}",
|
||||
"minimumRequiredVersion": "Gerekli minimum sürüm: {version}",
|
||||
"topUpstreams": "Öne çıkan üst kaynaklar",
|
||||
"averageUpstreamResponseTime": "Üst kaynak ortalama yanıt süresi"
|
||||
}
|
||||
"topUpstreams": "Öne çıkan DNS sunucuları",
|
||||
"averageUpstreamResponseTime": "DNS sunucusu ortalama işlem süresi" ,
|
||||
"dhcpNotAvailable": "DHCP sunucusu kullanılamıyor.",
|
||||
"osServerInstalledIncompatible": "AdGuard Home, işletim sisteminizde DHCP sunucusu çalıştıramıyor.",
|
||||
"resetSettings": "Ayarları sıfırla",
|
||||
"resetEncryptionSettingsDescription": "Şifreleme ayarlarını sıfırlamak istediğinizden emin misiniz?",
|
||||
"resettingConfig": "Yapılandırma sıfırlanıyor...",
|
||||
"configurationResetSuccessfully": "Yapılandırma başarıyla sıfırlandı",
|
||||
"configurationResetError": "Yapılandırma sıfırlanamadı",
|
||||
"testUpstreamDnsServers": "DNS sunucusunu test et",
|
||||
"errorTestUpstreamDns": "DNS sunucularını test ederken hata oluştu.",
|
||||
"useCustomIpEdns": "EDNS için özel IP kullan",
|
||||
"useCustomIpEdnsDescription": "EDNS için özel IP kullanımına izin ver",
|
||||
"sortingOptions": "Sıralama seçenekleri",
|
||||
"fromHighestToLowest": "Yüksekten düşüğe",
|
||||
"fromLowestToHighest": "Düşükten yükseğe",
|
||||
"queryLogsAndStatistics": "Sorgu günlüğü ve istatistikler",
|
||||
"ignoreClientQueryLog": "Sorgu günlüğünde bu istemciyi yoksay",
|
||||
"ignoreClientStatistics": "İstatistiklerde bu istemciyi yoksay",
|
||||
"savingChanges": "Değişiklikler kaydediliyor...",
|
||||
"fallbackDnsServers": "Yedek DNS sunucuları",
|
||||
"fallbackDnsServersDescription": "Yedek DNS sunucularını yapılandır",
|
||||
"fallbackDnsServersInfo": "Üst DNS sunucuları yanıt vermediğinde kullanılan yedek DNS sunucularının listesi. Sözdizimi, yukarıdaki ana üst kaynak alanıyla aynıdır.",
|
||||
"noFallbackDnsAdded": "Yedek DNS sunucusu eklenmedi.",
|
||||
"blockedResponseTtl": "Engellenen yanıtın kullanım süresi",
|
||||
"blockedResponseTtlDescription": "İstemcilerin filtrelenmiş bir yanıtı kaç saniye süreyle önbelleğe alması gerektiğini belirtir",
|
||||
"invalidValue": "Geçersiz değer",
|
||||
"noDataChart": "Bu grafiği görüntüleyecek veri yok.",
|
||||
"noData": "Veri yok",
|
||||
"unblockClient": "İstemci engelini kaldır",
|
||||
"blockingClient": "İstemci engelleniyor...",
|
||||
"unblockingClient": "İstemci engeli kaldırılıyor...",
|
||||
"upstreamDnsCacheConfiguration": "DNS önbellek yapılandırması",
|
||||
"enableDnsCachingClient": "Bu istemci için DNS önbelleğe almayı etkinleştir",
|
||||
"dnsCacheSize": "DNS önbellek boyutu (bayt cinsinden)",
|
||||
"nameInvalid": "Ad gereklidir",
|
||||
"oneIdentifierRequired": "En az bir tanımlayıcı gereklidir",
|
||||
"dnsCacheNumber": "DNS önbellek boyutu bir rakam içermelidir",
|
||||
"errors": "Hatalar",
|
||||
"redirectHttpsWarning": "AdGuard Home sunucunuzda \"Otomatik olarak HTTPS'e yönlendir\" seçeneğini etkinleştirdiyseniz, bir HTTPS bağlantısı seçmeli ve sunucunuzun HTTPS bağlantı noktasını kullanmalısınız.",
|
||||
"logsSettingsDescription": "Sorgu günlüklerini yapılandır",
|
||||
"ignoredDomains": "Yok sayılan alan adları",
|
||||
"noIgnoredDomainsAdded": "Yok sayılacak alan adı eklenmedi",
|
||||
"pauseServiceBlocking": "Hizmet engellemeyi duraklat",
|
||||
"newSchedule": "Yeni program",
|
||||
"editSchedule": "Programı düzenle",
|
||||
"timezone": "Zaman dilimi",
|
||||
"monday": "Pazartesi",
|
||||
"tuesday": "Salı",
|
||||
"wednesday": "Çarşamba",
|
||||
"thursday": "Perşembe",
|
||||
"friday": "Cuma",
|
||||
"saturday": "Cumartesi",
|
||||
"sunday": "Pazar",
|
||||
"from": "Başlangıç",
|
||||
"to": "Bitiş",
|
||||
"selectStartTime": "Başlangıç zamanını seç",
|
||||
"selectEndTime": "Bitiş zamanını seç",
|
||||
"startTimeBeforeEndTime": "Başlangıç zamanı bitiş zamanından önce olmalıdır.",
|
||||
"noBlockingScheduleThisDevice": "Bu cihaz için herhangi bir engelleme programı bulunmamaktadır.",
|
||||
"selectTimezone": "Bir zaman dilimi seç",
|
||||
"selectClientsFiltersInfo": "Görüntülemek istediğiniz istemcileri seçin. Hiçbir istemci seçilmemişse, hepsi görüntülenecektir.",
|
||||
"noDataThisSection": "Bu bölüm için veri yok.",
|
||||
"statisticsSettings": "İstatistik ayarları",
|
||||
"statisticsSettingsDescription": "İstatistikler için veri toplamayı yapılandır",
|
||||
"loadingStatisticsSettings": "İstatistik ayarları yükleniyor...",
|
||||
"statisticsSettingsLoadError": "İstatistik ayarları yüklenirken bir hata oluştu.",
|
||||
"customTimeInHours": "Özel zaman (saat olarak)",
|
||||
"invalidTime": "Geçersiz zaman",
|
||||
"removeDomain": "Alan adını kaldır",
|
||||
"addDomain": "Alan adı ekle",
|
||||
"notLess1Hour": "Zaman 1 saatten az olamaz",
|
||||
"rateLimit": "Hız sınırı",
|
||||
"subnetPrefixLengthIpv4": "IPv4 için alt ağ önek uzunluğu",
|
||||
"subnetPrefixLengthIpv6": "IPv6 için alt ağ önek uzunluğu",
|
||||
"rateLimitAllowlist": "Hız sınırlama izin listesi",
|
||||
"rateLimitAllowlistDescription": "Hız sınırlamasından hariç tutulan IP adresleri",
|
||||
"dnsOptions": "DNS ayarları",
|
||||
"editor": "Editör",
|
||||
"editCustomRules": "Özel kuralları düzenle",
|
||||
"savingCustomRules": "Özel kurallar kaydediliyor...",
|
||||
"customRulesUpdatedSuccessfully": "Özel kurallar başarıyla güncellendi",
|
||||
"customRulesNotUpdated": "Özel kurallar güncellenemedi",
|
||||
"reorder": "Sırala",
|
||||
"showHide": "Göster/gizle",
|
||||
"noElementsReorderMessage": "Burada yeniden sıralamak için göster/gizle sekmesindeki bazı öğeleri etkinleştirin.",
|
||||
"enablePlainDns": "Düz DNS'i etkinleştir",
|
||||
"enablePlainDnsDescription": "Düz DNS varsayılan olarak etkindir. Tüm aygıtları şifrelenmiş DNS kullanmaya zorlamak için bunu devre dışı bırakabilirsiniz. Bunu yapmak için en az bir şifrelenmiş DNS protokolünü etkinleştirmeniz gerekir."
|
||||
}
|
|
@ -10,6 +10,7 @@ import 'package:dynamic_color/dynamic_color.dart';
|
|||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:store_checker/store_checker.dart';
|
||||
import 'package:window_size/window_size.dart';
|
||||
|
@ -31,8 +32,8 @@ import 'package:adguard_home_manager/providers/servers_provider.dart';
|
|||
import 'package:adguard_home_manager/constants/colors.dart';
|
||||
import 'package:adguard_home_manager/config/globals.dart';
|
||||
import 'package:adguard_home_manager/config/theme.dart';
|
||||
import 'package:adguard_home_manager/classes/http_override.dart';
|
||||
import 'package:adguard_home_manager/services/db/database.dart';
|
||||
import 'package:adguard_home_manager/classes/http_override.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
@ -48,8 +49,12 @@ void main() async {
|
|||
}
|
||||
|
||||
await dotenv.load(fileName: '.env');
|
||||
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
|
||||
final AppConfigProvider appConfigProvider = AppConfigProvider();
|
||||
final AppConfigProvider appConfigProvider = AppConfigProvider(
|
||||
sharedPreferencesInstance: sharedPreferences
|
||||
);
|
||||
final ServersProvider serversProvider = ServersProvider();
|
||||
final StatusProvider statusProvider = StatusProvider();
|
||||
final ClientsProvider clientsProvider = ClientsProvider();
|
||||
|
@ -69,9 +74,7 @@ void main() async {
|
|||
appConfigProvider.setIosInfo(iosInfo);
|
||||
}
|
||||
|
||||
final dbData = await loadDb(appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31);
|
||||
|
||||
if (dbData['appConfig']['overrideSslCheck'] == 1) {
|
||||
if (sharedPreferences.getBool('overrideSslCheck') == true) {
|
||||
HttpOverrides.global = MyHttpOverrides();
|
||||
}
|
||||
|
||||
|
@ -80,10 +83,12 @@ void main() async {
|
|||
appConfigProvider.setInstallationSource(installationSource);
|
||||
}
|
||||
|
||||
final dbData = await loadDb();
|
||||
serversProvider.setDbInstance(dbData['dbInstance']);
|
||||
appConfigProvider.saveFromDb(dbData['dbInstance'], dbData['appConfig']);
|
||||
serversProvider.saveFromDb(dbData['servers']);
|
||||
|
||||
appConfigProvider.saveFromSharedPreferences();
|
||||
|
||||
PackageInfo appInfo = await PackageInfo.fromPlatform();
|
||||
appConfigProvider.setAppInfo(appInfo);
|
||||
|
||||
|
@ -208,42 +213,45 @@ class _MainState extends State<Main> {
|
|||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
return DynamicColorBuilder(
|
||||
builder: (lightDynamic, darkDynamic) => MaterialApp(
|
||||
title: 'AdGuard Home Manager',
|
||||
theme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31
|
||||
? appConfigProvider.useDynamicColor == true
|
||||
? lightTheme(lightDynamic)
|
||||
: lightThemeOldVersions(colors[appConfigProvider.staticColor])
|
||||
: lightThemeOldVersions(colors[appConfigProvider.staticColor]),
|
||||
darkTheme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31
|
||||
? appConfigProvider.useDynamicColor == true
|
||||
? darkTheme(darkDynamic)
|
||||
: darkThemeOldVersions(colors[appConfigProvider.staticColor])
|
||||
: darkThemeOldVersions(colors[appConfigProvider.staticColor]),
|
||||
themeMode: appConfigProvider.selectedTheme,
|
||||
debugShowCheckedModeBanner: false,
|
||||
localizationsDelegates: const [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
AppLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [
|
||||
Locale('en', ''),
|
||||
Locale('es', ''),
|
||||
Locale('zh', ''),
|
||||
Locale('zh', 'CN'),
|
||||
Locale('pl', ''),
|
||||
Locale('tr', ''),
|
||||
Locale('ru', '')
|
||||
],
|
||||
scaffoldMessengerKey: scaffoldMessengerKey,
|
||||
navigatorKey: globalNavigatorKey,
|
||||
builder: (context, child) => CustomMenuBar(
|
||||
child: child!,
|
||||
),
|
||||
home: const Layout(),
|
||||
),
|
||||
builder: (lightDynamic, darkDynamic) {
|
||||
appConfigProvider.setSupportsDynamicTheme(lightDynamic != null && darkDynamic != null);
|
||||
return MaterialApp(
|
||||
title: 'AdGuard Home Manager',
|
||||
theme: lightDynamic != null
|
||||
? appConfigProvider.useDynamicColor == true
|
||||
? lightTheme(lightDynamic)
|
||||
: lightThemeOldVersions(colors[appConfigProvider.staticColor])
|
||||
: lightThemeOldVersions(colors[appConfigProvider.staticColor]),
|
||||
darkTheme: darkDynamic != null
|
||||
? appConfigProvider.useDynamicColor == true
|
||||
? darkTheme(darkDynamic)
|
||||
: darkThemeOldVersions(colors[appConfigProvider.staticColor])
|
||||
: darkThemeOldVersions(colors[appConfigProvider.staticColor]),
|
||||
themeMode: appConfigProvider.selectedTheme,
|
||||
debugShowCheckedModeBanner: false,
|
||||
localizationsDelegates: const [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
AppLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [
|
||||
Locale('en', ''),
|
||||
Locale('es', ''),
|
||||
Locale('zh', ''),
|
||||
Locale('zh', 'CN'),
|
||||
Locale('pl', ''),
|
||||
Locale('tr', ''),
|
||||
Locale('ru', '')
|
||||
],
|
||||
scaffoldMessengerKey: scaffoldMessengerKey,
|
||||
navigatorKey: globalNavigatorKey,
|
||||
builder: (context, child) => CustomMenuBar(
|
||||
child: child!,
|
||||
),
|
||||
home: const Layout(),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -130,7 +130,9 @@ class Client {
|
|||
ignoreStatistics: json["ignore_statistics"],
|
||||
upstreamsCacheEnabled: json["upstreams_cache_enabled"],
|
||||
upstreamsCacheSize: json["upstreams_cache_size"],
|
||||
blockedServicesSchedule: BlockedServicesSchedule.fromJson(json["blocked_services_schedule"])
|
||||
blockedServicesSchedule: json["blocked_services_schedule"] != null
|
||||
? BlockedServicesSchedule.fromJson(json["blocked_services_schedule"])
|
||||
: null
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
|
|
|
@ -23,6 +23,9 @@ class DnsInfo {
|
|||
String blockingIpv6;
|
||||
List<String> defaultLocalPtrUpstreams;
|
||||
int? blockedResponseTtl;
|
||||
int? ratelimitSubnetLenIpv4;
|
||||
int? ratelimitSubnetLenIpv6;
|
||||
List<String>? ratelimitWhitelist;
|
||||
|
||||
DnsInfo({
|
||||
required this.upstreamDns,
|
||||
|
@ -49,6 +52,9 @@ class DnsInfo {
|
|||
required this.blockingIpv6,
|
||||
required this.defaultLocalPtrUpstreams,
|
||||
required this.blockedResponseTtl,
|
||||
required this.ratelimitSubnetLenIpv4,
|
||||
required this.ratelimitSubnetLenIpv6,
|
||||
required this.ratelimitWhitelist,
|
||||
});
|
||||
|
||||
factory DnsInfo.fromJson(Map<String, dynamic> json) => DnsInfo(
|
||||
|
@ -75,7 +81,10 @@ class DnsInfo {
|
|||
blockingIpv4: json["blocking_ipv4"],
|
||||
blockingIpv6: json["blocking_ipv6"],
|
||||
defaultLocalPtrUpstreams: json["default_local_ptr_upstreams"] != null ? List<String>.from(json["default_local_ptr_upstreams"].map((x) => x)) : [],
|
||||
blockedResponseTtl: json["blocked_response_ttl"]
|
||||
blockedResponseTtl: json["blocked_response_ttl"],
|
||||
ratelimitSubnetLenIpv4: json["ratelimit_subnet_len_ipv4"],
|
||||
ratelimitSubnetLenIpv6: json["ratelimit_subnet_len_ipv6"],
|
||||
ratelimitWhitelist: json["ratelimit_whitelist"] != null ? List<String>.from(json["ratelimit_whitelist"].map((x) => x)) : [],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
|
@ -102,6 +111,9 @@ class DnsInfo {
|
|||
"blocking_ipv4": blockingIpv4,
|
||||
"blocking_ipv6": blockingIpv6,
|
||||
"default_local_ptr_upstreams": List<dynamic>.from(defaultLocalPtrUpstreams.map((x) => x)),
|
||||
"blocked_response_ttl": blockedResponseTtl
|
||||
"blocked_response_ttl": blockedResponseTtl,
|
||||
"ratelimit_subnet_len_ipv4": ratelimitSubnetLenIpv4,
|
||||
"ratelimit_subnet_len_ipv6": ratelimitSubnetLenIpv6,
|
||||
"ratelimit_whitelist": ratelimitWhitelist != null ? List<String>.from(ratelimitWhitelist!.map((x) => x)) : null,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ class EncryptionData {
|
|||
final String certificatePath;
|
||||
final String privateKeyPath;
|
||||
final bool privateKeySaved;
|
||||
final bool? servePlainDns;
|
||||
|
||||
EncryptionData({
|
||||
required this.validCert,
|
||||
|
@ -65,6 +66,7 @@ class EncryptionData {
|
|||
required this.certificatePath,
|
||||
required this.privateKeyPath,
|
||||
required this.privateKeySaved,
|
||||
required this.servePlainDns,
|
||||
});
|
||||
|
||||
|
||||
|
@ -93,6 +95,7 @@ class EncryptionData {
|
|||
certificatePath: json["certificate_path"],
|
||||
privateKeyPath: json["private_key_path"],
|
||||
privateKeySaved: json["private_key_saved"],
|
||||
servePlainDns: json["serve_plain_dns"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
|
@ -120,6 +123,7 @@ class EncryptionData {
|
|||
"certificate_path": certificatePath,
|
||||
"private_key_path": privateKeyPath,
|
||||
"private_key_saved": privateKeySaved,
|
||||
"serve_plain_dns": servePlainDns,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,6 @@ class ServerStatus {
|
|||
safeSearchPixabay: json['safeSearch']['pixabay'],
|
||||
safeSearchYandex: json['safeSearch']['yandex'],
|
||||
safeSearchYoutube: json['safeSearch']['youtube'],
|
||||
dhcpAvailable: json['status']['dhcp_available']
|
||||
dhcpAvailable: json['status']['dhcp_available'] ?? false
|
||||
);
|
||||
}
|
|
@ -1,22 +1,23 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:store_checker/store_checker.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:sqflite/sqlite_api.dart';
|
||||
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/config/home_top_items_default_order.dart';
|
||||
import 'package:adguard_home_manager/models/github_release.dart';
|
||||
import 'package:adguard_home_manager/services/db/queries.dart';
|
||||
import 'package:adguard_home_manager/functions/conversions.dart';
|
||||
import 'package:adguard_home_manager/models/app_log.dart';
|
||||
|
||||
class AppConfigProvider with ChangeNotifier {
|
||||
Database? _dbInstance;
|
||||
final SharedPreferences sharedPreferencesInstance;
|
||||
|
||||
AppConfigProvider({
|
||||
required this.sharedPreferencesInstance
|
||||
});
|
||||
|
||||
PackageInfo? _appInfo;
|
||||
AndroidDeviceInfo? _androidDeviceInfo;
|
||||
|
@ -28,6 +29,7 @@ class AppConfigProvider with ChangeNotifier {
|
|||
|
||||
bool _showingSnackbar = false;
|
||||
|
||||
bool _supportsDynamicTheme = true;
|
||||
int _selectedTheme = 0;
|
||||
bool _useDynamicColor = true;
|
||||
int _staticColor = 0;
|
||||
|
@ -38,19 +40,19 @@ class AppConfigProvider with ChangeNotifier {
|
|||
|
||||
List<HomeTopItems> _homeTopItemsOrder = homeTopItemsDefaultOrder;
|
||||
|
||||
int _hideServerAddress = 0;
|
||||
bool _hideServerAddress = false;
|
||||
|
||||
final List<AppLog> _logs = [];
|
||||
|
||||
int _overrideSslCheck = 0;
|
||||
bool _overrideSslCheck = false;
|
||||
|
||||
int _hideZeroValues = 0;
|
||||
bool _hideZeroValues = false;
|
||||
|
||||
int _showTimeLogs = 0;
|
||||
bool _showTimeLogs = false;
|
||||
|
||||
int _showIpLogs = 0;
|
||||
bool _showIpLogs = false;
|
||||
|
||||
int _combinedChartHome = 0;
|
||||
bool _combinedChartHome = false;
|
||||
|
||||
String? _doNotRememberVersion;
|
||||
|
||||
|
@ -88,6 +90,10 @@ class AppConfigProvider with ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
bool get supportsDynamicTheme {
|
||||
return _supportsDynamicTheme;
|
||||
}
|
||||
|
||||
int get selectedThemeNumber {
|
||||
return _selectedTheme;
|
||||
}
|
||||
|
@ -105,11 +111,11 @@ class AppConfigProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
bool get overrideSslCheck {
|
||||
return _overrideSslCheck == 1 ? true : false;
|
||||
return _overrideSslCheck;
|
||||
}
|
||||
|
||||
bool get hideZeroValues {
|
||||
return _hideZeroValues == 1 ? true : false;
|
||||
return _hideZeroValues;
|
||||
}
|
||||
|
||||
int get selectedScreen {
|
||||
|
@ -133,15 +139,15 @@ class AppConfigProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
bool get showTimeLogs {
|
||||
return _showTimeLogs == 1 ? true : false;
|
||||
return _showTimeLogs;
|
||||
}
|
||||
|
||||
bool get showIpLogs {
|
||||
return _showIpLogs == 1 ? true : false;
|
||||
return _showIpLogs;
|
||||
}
|
||||
|
||||
bool get combinedChartHome {
|
||||
return _combinedChartHome == 1 ? true : false;
|
||||
return _combinedChartHome;
|
||||
}
|
||||
|
||||
String? get doNotRememberVersion {
|
||||
|
@ -165,11 +171,11 @@ class AppConfigProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
bool get hideServerAddress {
|
||||
return _hideServerAddress == 1 ? true : false;
|
||||
return _hideServerAddress;
|
||||
}
|
||||
|
||||
void setDbInstance(Database db) {
|
||||
_dbInstance = db;
|
||||
void setSupportsDynamicTheme(bool value) {
|
||||
_supportsDynamicTheme = value;
|
||||
}
|
||||
|
||||
void setAppInfo(PackageInfo appInfo) {
|
||||
|
@ -227,206 +233,145 @@ class AppConfigProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> setOverrideSslCheck(bool status) async {
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'overrideSslCheck',
|
||||
value: status == true ? 1 : 0
|
||||
);
|
||||
if (updated == true) {
|
||||
_overrideSslCheck = status == true ? 1 : 0;
|
||||
try {
|
||||
sharedPreferencesInstance.setBool('overrideSslCheck', status);
|
||||
_overrideSslCheck = status;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} catch (e, stackTrace) {
|
||||
Sentry.captureException(e, stackTrace: stackTrace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> setHideZeroValues(bool status) async {
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'hideZeroValues',
|
||||
value: status == true ? 1 : 0
|
||||
);
|
||||
if (updated == true) {
|
||||
_hideZeroValues = status == true ? 1 : 0;
|
||||
try {
|
||||
sharedPreferencesInstance.setBool('hideZeroValues', status);
|
||||
_hideZeroValues = status;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} catch (e, stackTrace) {
|
||||
Sentry.captureException(e, stackTrace: stackTrace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> setshowTimeLogs(bool status) async {
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'showTimeLogs',
|
||||
value: status == true ? 1 : 0
|
||||
);
|
||||
if (updated == true) {
|
||||
_showTimeLogs = status == true ? 1 : 0;
|
||||
try {
|
||||
sharedPreferencesInstance.setBool('showTimeLogs', status);
|
||||
_showTimeLogs = status;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} catch (e, stackTrace) {
|
||||
Sentry.captureException(e, stackTrace: stackTrace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> setShowIpLogs(bool status) async {
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'showIpLogs',
|
||||
value: status == true ? 1 : 0
|
||||
);
|
||||
if (updated == true) {
|
||||
_showIpLogs = status == true ? 1 : 0;
|
||||
try {
|
||||
sharedPreferencesInstance.setBool('showIpLogs', status);
|
||||
_showIpLogs = status;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} catch (e, stackTrace) {
|
||||
Sentry.captureException(e, stackTrace: stackTrace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> setSelectedTheme(int value) async {
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'theme',
|
||||
value: value
|
||||
);
|
||||
if (updated == true) {
|
||||
try {
|
||||
sharedPreferencesInstance.setInt('selectedTheme', value);
|
||||
_selectedTheme = value;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} catch (e, stackTrace) {
|
||||
Sentry.captureException(e, stackTrace: stackTrace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> setUseDynamicColor(bool value) async {
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'useDynamicColor',
|
||||
value: value == true ? 1 : 0
|
||||
);
|
||||
if (updated == true) {
|
||||
try {
|
||||
sharedPreferencesInstance.setBool('useDynamicColor', value);
|
||||
_useDynamicColor = value;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> setUseThemeColorForStatus(bool value) async {
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'useThemeColorForStatus',
|
||||
value: value == true ? 1 : 0
|
||||
);
|
||||
if (updated == true) {
|
||||
_useThemeColorForStatus = value;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} catch (e, stackTrace) {
|
||||
Sentry.captureException(e, stackTrace: stackTrace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> setCombinedChartHome(bool value) async {
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'combinedChart',
|
||||
value: value == true ? 1 : 0
|
||||
);
|
||||
if (updated == true) {
|
||||
_combinedChartHome = value == true ? 1 : 0;
|
||||
try {
|
||||
sharedPreferencesInstance.setBool('combinedChart', value);
|
||||
_combinedChartHome = value;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} catch (e, stackTrace) {
|
||||
Sentry.captureException(e, stackTrace: stackTrace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> setStaticColor(int value) async {
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'staticColor',
|
||||
value: value
|
||||
);
|
||||
if (updated == true) {
|
||||
try {
|
||||
sharedPreferencesInstance.setInt('staticColor', value);
|
||||
_staticColor = value;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} catch (e, stackTrace) {
|
||||
Sentry.captureException(e, stackTrace: stackTrace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> setHomeTopItemsOrder(List<HomeTopItems> order) async {
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'homeTopItemsOrder',
|
||||
value: jsonEncode(List<String>.from(order.map((e) => e.name)))
|
||||
);
|
||||
if (updated == true) {
|
||||
try {
|
||||
sharedPreferencesInstance.setStringList('homeTopItemsOrder', List<String>.from(order.map((e) => e.name)));
|
||||
_homeTopItemsOrder = order;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} catch (e, stackTrace) {
|
||||
Sentry.captureException(e, stackTrace: stackTrace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> setHideServerAddress(bool value) async {
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'hideServerAddress',
|
||||
value: value == true ? 1 : 0
|
||||
);
|
||||
if (updated == true) {
|
||||
_hideServerAddress = value == true ? 1 : 0;
|
||||
try {
|
||||
sharedPreferencesInstance.setBool('hideServerAddress', value);
|
||||
_hideServerAddress = value;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} catch (e, stackTrace) {
|
||||
Sentry.captureException(e, stackTrace: stackTrace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> setDoNotRememberVersion(String value) async {
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'doNotRememberVersion',
|
||||
value: value
|
||||
);
|
||||
final updated = await sharedPreferencesInstance.setString('hideServerAddress', value);
|
||||
return updated;
|
||||
}
|
||||
|
||||
void saveFromDb(Database dbInstance, Map<String, dynamic> dbData) {
|
||||
_selectedTheme = dbData['theme'] ?? 0;
|
||||
_overrideSslCheck = dbData['overrideSslCheck'] ?? 0;
|
||||
_hideZeroValues = dbData['hideZeroValues'];
|
||||
_useDynamicColor = convertFromIntToBool(dbData['useDynamicColor'])!;
|
||||
_staticColor = dbData['staticColor'] ?? 0;
|
||||
_useThemeColorForStatus = dbData['useThemeColorForStatus'] != null ? convertFromIntToBool(dbData['useThemeColorForStatus'])! : false;
|
||||
_showTimeLogs = dbData['showTimeLogs'] ?? 0;
|
||||
_doNotRememberVersion = dbData['doNotRememberVersion'];
|
||||
_showIpLogs = dbData['showIpLogs'] ?? 0;
|
||||
_combinedChartHome = dbData['combinedChart'] ?? 0;
|
||||
_hideServerAddress = dbData['hideServerAddress'];
|
||||
if (dbData['homeTopItemsOrder'] != null) {
|
||||
|
||||
void saveFromSharedPreferences() {
|
||||
_selectedTheme = sharedPreferencesInstance.getInt('selectedTheme') ?? 0;
|
||||
_overrideSslCheck = sharedPreferencesInstance.getBool('overrideSslCheck') ?? false;
|
||||
_hideZeroValues = sharedPreferencesInstance.getBool('hideZeroValues') ?? false;
|
||||
_useDynamicColor = sharedPreferencesInstance.getBool('useDynamicColor') ?? true;
|
||||
_staticColor = sharedPreferencesInstance.getInt('staticColor') ?? 0;
|
||||
_showTimeLogs = sharedPreferencesInstance.getBool('showTimeLogs') ?? false;
|
||||
_doNotRememberVersion = sharedPreferencesInstance.getString('doNotRememberVersion');
|
||||
_showIpLogs = sharedPreferencesInstance.getBool('showIpLogs') ?? false;
|
||||
_combinedChartHome = sharedPreferencesInstance.getBool('combinedChart') ?? false;
|
||||
_hideServerAddress = sharedPreferencesInstance.getBool('hideServerAddress') ?? false;
|
||||
if (sharedPreferencesInstance.getStringList('homeTopItemsOrder') != null) {
|
||||
try {
|
||||
final itemsOrder = List<HomeTopItems>.from(
|
||||
List<String>.from(jsonDecode(dbData['homeTopItemsOrder'])).map((e) {
|
||||
_homeTopItemsOrder = List<HomeTopItems>.from(
|
||||
List<String>.from(sharedPreferencesInstance.getStringList('homeTopItemsOrder')!).map((e) {
|
||||
switch (e) {
|
||||
case 'queriedDomains':
|
||||
return HomeTopItems.queriedDomains;
|
||||
|
@ -448,18 +393,10 @@ class AppConfigProvider with ChangeNotifier {
|
|||
}
|
||||
}).where((e) => e != null).toList()
|
||||
);
|
||||
final missingItems = homeTopItemsDefaultOrder.where((e) => !itemsOrder.contains(e));
|
||||
_homeTopItemsOrder = [
|
||||
...itemsOrder,
|
||||
...missingItems
|
||||
];
|
||||
} catch (e) {
|
||||
Sentry.captureException(e);
|
||||
_homeTopItemsOrder = homeTopItemsDefaultOrder;
|
||||
}
|
||||
}
|
||||
|
||||
_dbInstance = dbInstance;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
|
@ -162,6 +162,9 @@ class DnsProvider with ChangeNotifier {
|
|||
data.blockingIpv4 = value['blocking_ipv4'];
|
||||
data.blockingIpv6 = value['blocking_ipv6'];
|
||||
data.blockedResponseTtl = value['blocked_response_ttl'];
|
||||
data.ratelimitSubnetLenIpv4 = value['ratelimit_subnet_len_ipv4'];
|
||||
data.ratelimitSubnetLenIpv6 = value['ratelimit_subnet_len_ipv6'];
|
||||
data.ratelimitWhitelist = value['ratelimit_whitelist'];
|
||||
setDnsInfoData(data);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -289,6 +289,23 @@ class FilteringProvider with ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
Future<bool> setCustomRules(List<String> rules) async {
|
||||
final newRules = rules.where((r) => r != " " && r != "").toList();
|
||||
final result = await _serversProvider!.apiClient2!.setCustomRules(rules: newRules);
|
||||
|
||||
if (result.successful == true) {
|
||||
Filtering filteringData = filtering!;
|
||||
filteringData.userRules = newRules;
|
||||
_filtering = filteringData;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> addList({required String name, required String url, required String type}) async {
|
||||
final result1 = await _serversProvider!.apiClient2!.addFilteringList(
|
||||
data: {
|
||||
|
|
|
@ -6,7 +6,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/filters/modals/add_custom_rule.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/modals/add_custom_rule/edit_custom_rules.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/modals/add_custom_rule/add_custom_rule.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/details/add_list_modal.dart';
|
||||
|
||||
import 'package:adguard_home_manager/providers/filtering_provider.dart';
|
||||
|
@ -19,10 +20,10 @@ class AddFiltersButton extends StatelessWidget {
|
|||
final Widget Function(void Function()) widget;
|
||||
|
||||
const AddFiltersButton({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.type,
|
||||
required this.widget
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -56,6 +57,31 @@ class AddFiltersButton extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
void confirmEditCustomRules(List<String> rules) async {
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.savingCustomRules);
|
||||
|
||||
final result = await filteringProvider.setCustomRules(rules);
|
||||
|
||||
processModal.close();
|
||||
|
||||
if (!context.mounted) return;
|
||||
if (result == true) {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.customRulesUpdatedSuccessfully,
|
||||
color: Colors.green
|
||||
);
|
||||
}
|
||||
else {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.customRulesNotUpdated,
|
||||
color: Colors.red
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void openAddCustomRule() {
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
|
@ -83,6 +109,33 @@ class AddFiltersButton extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
void openEditCustomRule() {
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
barrierColor: !(width > 700 || !(Platform.isAndroid || Platform.isIOS))
|
||||
?Colors.transparent
|
||||
: Colors.black54,
|
||||
transitionBuilder: (context, anim1, anim2, child) {
|
||||
return SlideTransition(
|
||||
position: Tween(
|
||||
begin: const Offset(0, 1),
|
||||
end: const Offset(0, 0)
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: anim1,
|
||||
curve: Curves.easeInOutCubicEmphasized
|
||||
)
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
pageBuilder: (context, animation, secondaryAnimation) => EditCustomRules(
|
||||
fullScreen: !(width > 700 || !(Platform.isAndroid || Platform.isIOS)),
|
||||
onConfirm: confirmEditCustomRules,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void confirmAddList({required String name, required String url, required String type}) async {
|
||||
ProcessModal processModal = ProcessModal();
|
||||
processModal.open(AppLocalizations.of(context)!.addingList);
|
||||
|
@ -91,6 +144,7 @@ class AddFiltersButton extends StatelessWidget {
|
|||
|
||||
processModal.close();
|
||||
|
||||
if (!context.mounted) return;
|
||||
if (result['success'] == true) {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
|
@ -147,10 +201,25 @@ class AddFiltersButton extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
return widget(
|
||||
type == 'blacklist' || type == 'whitelist'
|
||||
? () => openAddWhitelistBlacklist()
|
||||
: () => openAddCustomRule(),
|
||||
);
|
||||
switch (type) {
|
||||
case 'blacklist':
|
||||
case 'whitelist':
|
||||
return widget(
|
||||
() => openAddWhitelistBlacklist(),
|
||||
);
|
||||
|
||||
case 'add_custom_rule':
|
||||
return widget(
|
||||
() => openAddCustomRule(),
|
||||
);
|
||||
|
||||
case 'edit_custom_rule':
|
||||
return widget(
|
||||
() => openEditCustomRule(),
|
||||
);
|
||||
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,12 +20,12 @@ class CustomRulesList extends StatefulWidget {
|
|||
final void Function(String) onRemoveCustomRule;
|
||||
|
||||
const CustomRulesList({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.loadStatus,
|
||||
required this.scrollController,
|
||||
required this.data,
|
||||
required this.onRemoveCustomRule
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<CustomRulesList> createState() => _CustomRulesListState();
|
||||
|
@ -210,13 +210,26 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
|||
);
|
||||
}
|
||||
},
|
||||
fab: AddFiltersButton(
|
||||
type: 'custom_rule',
|
||||
widget: (fn) => FloatingActionButton(
|
||||
onPressed: fn,
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
fab: Column(
|
||||
children: [
|
||||
AddFiltersButton(
|
||||
type: 'edit_custom_rule',
|
||||
widget: (fn) => FloatingActionButton.small(
|
||||
onPressed: fn,
|
||||
child: const Icon(Icons.edit_rounded),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
AddFiltersButton(
|
||||
type: 'add_custom_rule',
|
||||
widget: (fn) => FloatingActionButton(
|
||||
onPressed: fn,
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
heightFabHidden: -120,
|
||||
fabVisible: isVisible,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,416 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:segmented_button_slide/segmented_button_slide.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/open_url.dart';
|
||||
import 'package:adguard_home_manager/constants/urls.dart';
|
||||
|
||||
class AddCustomRule extends StatefulWidget {
|
||||
final void Function(String) onConfirm;
|
||||
final bool fullScreen;
|
||||
|
||||
const AddCustomRule({
|
||||
super.key,
|
||||
required this.onConfirm,
|
||||
required this.fullScreen
|
||||
});
|
||||
|
||||
@override
|
||||
State<AddCustomRule> createState() => _AddCustomRuleState();
|
||||
}
|
||||
|
||||
enum BlockingPresets { block, unblock, custom }
|
||||
|
||||
class _AddCustomRuleState extends State<AddCustomRule> {
|
||||
final TextEditingController domainController = TextEditingController();
|
||||
String? domainError;
|
||||
|
||||
BlockingPresets preset = BlockingPresets.block;
|
||||
|
||||
bool addImportant = false;
|
||||
|
||||
bool checkValidValues() {
|
||||
if (
|
||||
domainController.text != '' &&
|
||||
domainError == null
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void validateDomain(String value) {
|
||||
final domainRegex = RegExp(r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$');
|
||||
if (domainRegex.hasMatch(value)) {
|
||||
setState(() => domainError = null);
|
||||
}
|
||||
else {
|
||||
setState(() => domainError = AppLocalizations.of(context)!.domainNotValid);
|
||||
}
|
||||
checkValidValues();
|
||||
}
|
||||
|
||||
String buildRule({String?value}) {
|
||||
String rule = "";
|
||||
|
||||
String fieldValue = value ?? domainController.text;
|
||||
|
||||
if (preset == BlockingPresets.block) {
|
||||
rule = "||${fieldValue.trim()}^";
|
||||
}
|
||||
else if (preset == BlockingPresets.unblock) {
|
||||
rule = "@@||${fieldValue.trim()}^";
|
||||
}
|
||||
else {
|
||||
rule = fieldValue.trim();
|
||||
}
|
||||
|
||||
if (addImportant == true) {
|
||||
rule = "$rule\$important";
|
||||
}
|
||||
|
||||
return rule;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
List<Widget> content() {
|
||||
return [
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 5
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
)
|
||||
),
|
||||
child: Text(
|
||||
buildRule(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(height: 30),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: TextFormField(
|
||||
controller: domainController,
|
||||
onChanged: (value) => setState(() => {}),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.link_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
errorText: domainError,
|
||||
labelText: AppLocalizations.of(context)!.domain,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(height: 30),
|
||||
SegmentedButtonSlide(
|
||||
entries: [
|
||||
SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.block),
|
||||
SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.unblock),
|
||||
SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.custom),
|
||||
],
|
||||
selectedEntry: preset.index,
|
||||
onChange: (v) => setState(() => preset = BlockingPresets.values[v]),
|
||||
colors: SegmentedButtonSlideColors(
|
||||
barColor: Theme.of(context).colorScheme.primary.withOpacity(0.2),
|
||||
backgroundSelectedColor: Theme.of(context).colorScheme.primary,
|
||||
foregroundSelectedColor: Theme.of(context).colorScheme.onPrimary,
|
||||
foregroundUnselectedColor: Theme.of(context).colorScheme.onSurface,
|
||||
hoverColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
textOverflow: TextOverflow.ellipsis,
|
||||
fontSize: 14,
|
||||
height: 40,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
),
|
||||
),
|
||||
Container(height: 20),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => setState(() => addImportant = !addImportant),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.addImportant,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: addImportant,
|
||||
onChanged: (value) => setState(() => addImportant = value),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.examples,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"||example.org^",
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.example1,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
"@@||example.org^",
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.example2,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
"! Here goes a comment",
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"# Also a comment",
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.example3,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
"/REGEX/",
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.example4,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(height: 20),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => openUrl(Urls.customRuleDocs),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.moreInformation,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 15),
|
||||
child: Icon(
|
||||
Icons.open_in_new,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(height: 20)
|
||||
];
|
||||
}
|
||||
|
||||
if (widget.fullScreen == true) {
|
||||
return Dialog.fullscreen(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: CloseButton(onPressed: () => Navigator.pop(context)),
|
||||
title: Text(AppLocalizations.of(context)!.addCustomRule),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: checkValidValues() == true
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm(buildRule());
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.check)
|
||||
),
|
||||
const SizedBox(width: 10)
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
children: content(),
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.clear_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.close,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.addCustomRule,
|
||||
style: const TextStyle(
|
||||
fontSize: 22
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
onPressed: checkValidValues() == true
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm(buildRule());
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.check)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
children: content(),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
326
lib/screens/filters/modals/add_custom_rule/add_custom_rule.dart
Normal file
326
lib/screens/filters/modals/add_custom_rule/add_custom_rule.dart
Normal file
|
@ -0,0 +1,326 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:segmented_button_slide/segmented_button_slide.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/filters/modals/add_custom_rule/custom_rule_docs.dart';
|
||||
|
||||
enum _BlockingPresets { block, unblock, custom }
|
||||
|
||||
class AddCustomRule extends StatefulWidget {
|
||||
final void Function(String) onConfirm;
|
||||
final bool fullScreen;
|
||||
|
||||
const AddCustomRule({
|
||||
super.key,
|
||||
required this.onConfirm,
|
||||
required this.fullScreen
|
||||
});
|
||||
|
||||
@override
|
||||
State<AddCustomRule> createState() => _AddCustomRuleState();
|
||||
}
|
||||
|
||||
class _AddCustomRuleState extends State<AddCustomRule> {
|
||||
final TextEditingController _domainController = TextEditingController();
|
||||
String? _domainError;
|
||||
|
||||
_BlockingPresets _preset = _BlockingPresets.block;
|
||||
|
||||
bool _addImportant = false;
|
||||
|
||||
bool _checkValidValues() {
|
||||
if (
|
||||
_domainController.text != '' &&
|
||||
_domainError == null
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void validateDomain(String value) {
|
||||
final domainRegex = RegExp(r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$');
|
||||
if (domainRegex.hasMatch(value)) {
|
||||
setState(() => _domainError = null);
|
||||
}
|
||||
else {
|
||||
setState(() => _domainError = AppLocalizations.of(context)!.domainNotValid);
|
||||
}
|
||||
_checkValidValues();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.fullScreen == true) {
|
||||
return Dialog.fullscreen(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: CloseButton(onPressed: () => Navigator.pop(context)),
|
||||
title: Text(AppLocalizations.of(context)!.addCustomRule),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _checkValidValues() == true
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm(
|
||||
_buildRule(
|
||||
domainController: _domainController,
|
||||
important: _addImportant,
|
||||
preset: _preset
|
||||
)
|
||||
);
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.check)
|
||||
),
|
||||
const SizedBox(width: 10)
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
children: [
|
||||
_CustomRuleEditor(
|
||||
domainController: _domainController,
|
||||
domainError: _domainError,
|
||||
important: _addImportant,
|
||||
preset: _preset,
|
||||
setImportant: (v) => setState(() => _addImportant = v),
|
||||
setPreset: (v) => setState(() => _preset = v),
|
||||
validateDomain: validateDomain
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.clear_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.close,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.addCustomRule,
|
||||
style: const TextStyle(
|
||||
fontSize: 22
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _checkValidValues() == true
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm(
|
||||
_buildRule(
|
||||
domainController: _domainController,
|
||||
important: _addImportant,
|
||||
preset: _preset
|
||||
)
|
||||
);
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.check)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
_CustomRuleEditor(
|
||||
domainController: _domainController,
|
||||
domainError: _domainError,
|
||||
important: _addImportant,
|
||||
preset: _preset,
|
||||
setImportant: (v) => setState(() => _addImportant = v),
|
||||
setPreset: (v) => setState(() => _preset = v),
|
||||
validateDomain: validateDomain
|
||||
)
|
||||
]
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomRuleEditor extends StatelessWidget {
|
||||
final TextEditingController domainController;
|
||||
final String? domainError;
|
||||
final bool important;
|
||||
final void Function(bool) setImportant;
|
||||
final _BlockingPresets preset;
|
||||
final void Function(_BlockingPresets) setPreset;
|
||||
final void Function(String) validateDomain;
|
||||
|
||||
const _CustomRuleEditor({
|
||||
required this.domainController,
|
||||
required this.domainError,
|
||||
required this.important,
|
||||
required this.setImportant,
|
||||
required this.preset,
|
||||
required this.setPreset,
|
||||
required this.validateDomain,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 5
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
)
|
||||
),
|
||||
child: Text(
|
||||
_buildRule(
|
||||
domainController: domainController,
|
||||
important: important,
|
||||
preset: preset,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(height: 30),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: TextFormField(
|
||||
controller: domainController,
|
||||
onChanged: validateDomain,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.link_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
errorText: domainError,
|
||||
labelText: AppLocalizations.of(context)!.domain,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(height: 30),
|
||||
SegmentedButtonSlide(
|
||||
entries: [
|
||||
SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.block),
|
||||
SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.unblock),
|
||||
SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.custom),
|
||||
],
|
||||
selectedEntry: preset.index,
|
||||
onChange: (v) => setPreset(_BlockingPresets.values[v]),
|
||||
colors: SegmentedButtonSlideColors(
|
||||
barColor: Theme.of(context).colorScheme.primary.withOpacity(0.2),
|
||||
backgroundSelectedColor: Theme.of(context).colorScheme.primary,
|
||||
foregroundSelectedColor: Theme.of(context).colorScheme.onPrimary,
|
||||
foregroundUnselectedColor: Theme.of(context).colorScheme.onSurface,
|
||||
hoverColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
textOverflow: TextOverflow.ellipsis,
|
||||
fontSize: 14,
|
||||
height: 40,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
),
|
||||
),
|
||||
Container(height: 20),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => setImportant(!important),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.addImportant,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: important,
|
||||
onChanged: setImportant,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(height: 20),
|
||||
const CustomRuleDocs(),
|
||||
Container(height: 20)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _buildRule({
|
||||
String? value,
|
||||
required TextEditingController domainController,
|
||||
required _BlockingPresets preset,
|
||||
required bool important
|
||||
}) {
|
||||
String rule = "";
|
||||
String fieldValue = value ?? domainController.text;
|
||||
if (preset == _BlockingPresets.block) {
|
||||
rule = "||${fieldValue.trim()}^";
|
||||
}
|
||||
else if (preset == _BlockingPresets.unblock) {
|
||||
rule = "@@||${fieldValue.trim()}^";
|
||||
}
|
||||
else {
|
||||
rule = fieldValue.trim();
|
||||
}
|
||||
if (important == true) {
|
||||
rule = "$rule\$important";
|
||||
}
|
||||
return rule;
|
||||
}
|
166
lib/screens/filters/modals/add_custom_rule/custom_rule_docs.dart
Normal file
166
lib/screens/filters/modals/add_custom_rule/custom_rule_docs.dart
Normal file
|
@ -0,0 +1,166 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/constants/urls.dart';
|
||||
import 'package:adguard_home_manager/functions/open_url.dart';
|
||||
|
||||
class CustomRuleDocs extends StatelessWidget {
|
||||
const CustomRuleDocs({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.examples,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"||example.org^",
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.example1,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
"@@||example.org^",
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.example2,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
"! Here goes a comment",
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"# Also a comment",
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.example3,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
"/REGEX/",
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.example4,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(height: 8),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => openUrl(Urls.customRuleDocs),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.moreInformation,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 15),
|
||||
child: Icon(
|
||||
Icons.open_in_new,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/filters/modals/add_custom_rule/custom_rule_docs.dart';
|
||||
|
||||
import 'package:adguard_home_manager/providers/filtering_provider.dart';
|
||||
|
||||
class EditCustomRules extends StatefulWidget {
|
||||
final bool fullScreen;
|
||||
final void Function(List<String>) onConfirm;
|
||||
|
||||
const EditCustomRules({
|
||||
super.key,
|
||||
required this.fullScreen,
|
||||
required this.onConfirm,
|
||||
});
|
||||
|
||||
@override
|
||||
State<EditCustomRules> createState() => _EditCustomRulesState();
|
||||
}
|
||||
|
||||
class _EditCustomRulesState extends State<EditCustomRules> {
|
||||
final _fieldController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final filteringProvider = Provider.of<FilteringProvider>(context, listen: false);
|
||||
print(filteringProvider.filtering!.userRules);
|
||||
if (filteringProvider.filtering != null) {
|
||||
_fieldController.text = filteringProvider.filtering!.userRules.join("\n");
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.fullScreen == true) {
|
||||
return Dialog.fullscreen(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: CloseButton(onPressed: () => Navigator.pop(context)),
|
||||
title: Text(AppLocalizations.of(context)!.editCustomRules),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm(_fieldController.text.split("\n"));
|
||||
},
|
||||
icon: const Icon(Icons.save_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.save,
|
||||
),
|
||||
const SizedBox(width: 10)
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
children: [
|
||||
_CustomRulesRawEditor(fieldController: _fieldController)
|
||||
]
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.clear_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.close,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.addCustomRule,
|
||||
style: const TextStyle(
|
||||
fontSize: 22
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm(_fieldController.text.split("\n"));
|
||||
},
|
||||
icon: const Icon(Icons.save_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.save,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
_CustomRulesRawEditor(fieldController: _fieldController)
|
||||
]
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomRulesRawEditor extends StatelessWidget {
|
||||
final TextEditingController fieldController;
|
||||
|
||||
const _CustomRulesRawEditor({
|
||||
required this.fieldController
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(
|
||||
height: 300,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||
child: TextField(
|
||||
controller: fieldController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
labelText: AppLocalizations.of(context)!.rules,
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always
|
||||
),
|
||||
autocorrect: false,
|
||||
expands: true,
|
||||
minLines: null,
|
||||
maxLines: null,
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const CustomRuleDocs(),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -7,10 +7,21 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|||
import 'package:adguard_home_manager/widgets/custom_checkbox_list_tile.dart';
|
||||
import 'package:adguard_home_manager/widgets/list_bottom_sheet.dart';
|
||||
|
||||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/logs_provider.dart';
|
||||
|
||||
class ClientsModal extends StatelessWidget {
|
||||
class _ClientLog {
|
||||
final String ip;
|
||||
final String? name;
|
||||
|
||||
const _ClientLog({
|
||||
required this.ip,
|
||||
required this.name
|
||||
});
|
||||
}
|
||||
|
||||
class ClientsModal extends StatefulWidget {
|
||||
final List<String>? value;
|
||||
final bool dialog;
|
||||
|
||||
|
@ -21,196 +32,278 @@ class ClientsModal extends StatelessWidget {
|
|||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final clientsProvider = Provider.of<ClientsProvider>(context);
|
||||
final logsProvider = Provider.of<LogsProvider>(context);
|
||||
|
||||
if (dialog == true) {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: _ModalContent(
|
||||
selectedClients: logsProvider.selectedClients,
|
||||
onClientsSelected: (v) => logsProvider.setSelectedClients(v),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return ListBottomSheet(
|
||||
icon: Icons.smartphone_rounded,
|
||||
title: AppLocalizations.of(context)!.clients,
|
||||
children: [
|
||||
Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_rounded,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Flexible(
|
||||
child: Text(AppLocalizations.of(context)!.selectClientsFiltersInfo)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
CustomCheckboxListTile(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24,
|
||||
top: 8,
|
||||
right: 12,
|
||||
bottom: 8
|
||||
),
|
||||
value: logsProvider.selectedClients.length == clientsProvider.clients!.autoClients.length,
|
||||
onChanged: (v) {
|
||||
if (v == true) {
|
||||
logsProvider.setSelectedClients(clientsProvider.clients!.autoClients.map((e) => e.ip).toList());
|
||||
}
|
||||
else {
|
||||
logsProvider.setSelectedClients([]);
|
||||
}
|
||||
},
|
||||
title: AppLocalizations.of(context)!.selectAll
|
||||
),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
primary: false,
|
||||
itemCount: clientsProvider.clients!.autoClients.length,
|
||||
itemBuilder: (context, index) => _ListItem(
|
||||
label: clientsProvider.clients!.autoClients[index].ip,
|
||||
checkboxActive: logsProvider.selectedClients.contains(clientsProvider.clients!.autoClients[index].ip),
|
||||
onChanged: (isSelected) {
|
||||
if (isSelected == true) {
|
||||
logsProvider.setSelectedClients([
|
||||
...logsProvider.selectedClients,
|
||||
clientsProvider.clients!.autoClients[index].ip
|
||||
]);
|
||||
}
|
||||
else {
|
||||
logsProvider.setSelectedClients(
|
||||
logsProvider.selectedClients.where(
|
||||
(item) => item != clientsProvider.clients!.autoClients[index].ip
|
||||
).toList()
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
State<ClientsModal> createState() => _ClientsModalState();
|
||||
}
|
||||
|
||||
class _ModalContent extends StatelessWidget {
|
||||
final List<String> selectedClients;
|
||||
final void Function(List<String>) onClientsSelected;
|
||||
class _ClientsModalState extends State<ClientsModal> {
|
||||
List<_ClientLog> _filteredClients = [];
|
||||
final _searchController = TextEditingController();
|
||||
|
||||
const _ModalContent({
|
||||
required this.selectedClients,
|
||||
required this.onClientsSelected,
|
||||
});
|
||||
@override
|
||||
void initState() {
|
||||
final clientsProvider = Provider.of<ClientsProvider>(context, listen: false);
|
||||
final statusProvider = Provider.of<StatusProvider>(context, listen: false);
|
||||
_filteredClients = clientsProvider.clients!.autoClients.map((e) {
|
||||
String? name;
|
||||
try {
|
||||
name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(e.ip)).name;
|
||||
} catch (e) {
|
||||
// ---- //
|
||||
}
|
||||
return _ClientLog(
|
||||
ip: e.ip,
|
||||
name: name
|
||||
);
|
||||
}).toList();
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final clientsProvider = Provider.of<ClientsProvider>(context);
|
||||
final logsProvider = Provider.of<LogsProvider>(context);
|
||||
final statusProvider = Provider.of<StatusProvider>(context);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
CloseButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.clients,
|
||||
style: const TextStyle(
|
||||
fontSize: 22
|
||||
),
|
||||
)
|
||||
],
|
||||
void onSearch(String value) {
|
||||
final filtered = clientsProvider.clients!.autoClients.map((e) {
|
||||
String? name;
|
||||
try {
|
||||
name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(e.ip)).name;
|
||||
} catch (e) {
|
||||
// ---- //
|
||||
}
|
||||
return _ClientLog(
|
||||
ip: e.ip,
|
||||
name: name
|
||||
);
|
||||
}).where(
|
||||
(c) => c.ip.contains(value.toLowerCase()) || (c.name != null && c.name!.toLowerCase().contains(value.toLowerCase()))
|
||||
).toList();
|
||||
setState(() => _filteredClients = filtered);
|
||||
}
|
||||
|
||||
if (widget.dialog == true) {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: ListView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_rounded,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
CloseButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.clients,
|
||||
style: const TextStyle(
|
||||
fontSize: 22
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Flexible(
|
||||
child: Text(AppLocalizations.of(context)!.selectClientsFiltersInfo)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: ListView(
|
||||
children: [
|
||||
_SearchField(
|
||||
controller: _searchController,
|
||||
onClear: () => setState(() => _searchController.text = ""),
|
||||
onSearch: onSearch
|
||||
),
|
||||
Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_rounded,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Flexible(
|
||||
child: Text(AppLocalizations.of(context)!.selectClientsFiltersInfo)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
CustomCheckboxListTile(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24,
|
||||
top: 8,
|
||||
right: 12,
|
||||
bottom: 8
|
||||
),
|
||||
value: logsProvider.selectedClients.length == clientsProvider.clients!.autoClients.length,
|
||||
onChanged: (v) {
|
||||
if (v == true) {
|
||||
logsProvider.setSelectedClients(clientsProvider.clients!.autoClients.map((e) => e.ip).toList());
|
||||
}
|
||||
else {
|
||||
logsProvider.setSelectedClients([]);
|
||||
}
|
||||
},
|
||||
title: AppLocalizations.of(context)!.selectAll
|
||||
),
|
||||
ListView.builder(
|
||||
primary: false,
|
||||
shrinkWrap: true,
|
||||
itemCount: _filteredClients.length,
|
||||
itemBuilder: (context, index) => _ListItem(
|
||||
label: _filteredClients[index].ip,
|
||||
checkboxActive: logsProvider.selectedClients.contains(_filteredClients[index].ip),
|
||||
onChanged: (isSelected) {
|
||||
if (isSelected == true) {
|
||||
logsProvider.setSelectedClients([
|
||||
...logsProvider.selectedClients,
|
||||
_filteredClients[index].ip
|
||||
]);
|
||||
}
|
||||
else {
|
||||
logsProvider.setSelectedClients(
|
||||
logsProvider.selectedClients.where(
|
||||
(item) => item != _filteredClients[index].ip
|
||||
).toList()
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
CustomCheckboxListTile(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24,
|
||||
top: 8,
|
||||
right: 12,
|
||||
bottom: 8
|
||||
),
|
||||
value: logsProvider.selectedClients.length == clientsProvider.clients!.autoClients.length,
|
||||
onChanged: (v) {
|
||||
if (v == true) {
|
||||
logsProvider.setSelectedClients(clientsProvider.clients!.autoClients.map((e) => e.ip).toList());
|
||||
}
|
||||
else {
|
||||
logsProvider.setSelectedClients([]);
|
||||
}
|
||||
},
|
||||
title: AppLocalizations.of(context)!.selectAll
|
||||
),
|
||||
ListView.builder(
|
||||
primary: false,
|
||||
shrinkWrap: true,
|
||||
itemCount: clientsProvider.clients!.autoClients.length,
|
||||
itemBuilder: (context, index) => _ListItem(
|
||||
label: clientsProvider.clients!.autoClients[index].ip,
|
||||
checkboxActive: selectedClients.contains(clientsProvider.clients!.autoClients[index].ip),
|
||||
onChanged: (isSelected) {
|
||||
if (isSelected == true) {
|
||||
onClientsSelected([
|
||||
...selectedClients,
|
||||
clientsProvider.clients!.autoClients[index].ip
|
||||
]);
|
||||
}
|
||||
else {
|
||||
onClientsSelected(
|
||||
selectedClients.where(
|
||||
(item) => item != clientsProvider.clients!.autoClients[index].ip
|
||||
).toList()
|
||||
);
|
||||
}
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
if (Platform.isIOS) const SizedBox(height: 16)
|
||||
],
|
||||
)
|
||||
),
|
||||
if (Platform.isIOS) const SizedBox(height: 16)
|
||||
],
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Padding(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: ListBottomSheet(
|
||||
icon: Icons.smartphone_rounded,
|
||||
title: AppLocalizations.of(context)!.clients,
|
||||
children: [
|
||||
_SearchField(
|
||||
controller: _searchController,
|
||||
onClear: () => setState(() => _searchController.text = ""),
|
||||
onSearch: onSearch
|
||||
),
|
||||
Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_rounded,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Flexible(
|
||||
child: Text(AppLocalizations.of(context)!.selectClientsFiltersInfo)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
CustomCheckboxListTile(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24,
|
||||
top: 8,
|
||||
right: 12,
|
||||
bottom: 8
|
||||
),
|
||||
value: logsProvider.selectedClients.length == clientsProvider.clients!.autoClients.length,
|
||||
onChanged: (v) {
|
||||
if (v == true) {
|
||||
logsProvider.setSelectedClients(clientsProvider.clients!.autoClients.map((e) => e.ip).toList());
|
||||
}
|
||||
else {
|
||||
logsProvider.setSelectedClients([]);
|
||||
}
|
||||
},
|
||||
title: AppLocalizations.of(context)!.selectAll
|
||||
),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
primary: false,
|
||||
itemCount: _filteredClients.length,
|
||||
itemBuilder: (context, index) => _ListItem(
|
||||
label: _filteredClients[index].ip,
|
||||
checkboxActive: logsProvider.selectedClients.contains(_filteredClients[index].ip),
|
||||
onChanged: (isSelected) {
|
||||
if (isSelected == true) {
|
||||
logsProvider.setSelectedClients([
|
||||
...logsProvider.selectedClients,
|
||||
_filteredClients[index].ip
|
||||
]);
|
||||
}
|
||||
else {
|
||||
logsProvider.setSelectedClients(
|
||||
logsProvider.selectedClients.where(
|
||||
(item) => item != _filteredClients[index].ip
|
||||
).toList()
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
]
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchField extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final void Function(String) onSearch;
|
||||
final void Function() onClear;
|
||||
|
||||
const _SearchField({
|
||||
required this.controller,
|
||||
required this.onClear,
|
||||
required this.onSearch,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
onChanged: onSearch,
|
||||
decoration: InputDecoration(
|
||||
hintText: AppLocalizations.of(context)!.search,
|
||||
prefixIcon: const Icon(Icons.search_rounded),
|
||||
border: InputBorder.none,
|
||||
filled: true,
|
||||
fillColor: Colors.grey.withOpacity(0.2),
|
||||
suffixIcon: controller.text != ""
|
||||
? IconButton(
|
||||
onPressed: onClear,
|
||||
icon: const Icon(
|
||||
Icons.close_rounded,
|
||||
size: 20,
|
||||
),
|
||||
tooltip: AppLocalizations.of(context)!.clearSearch,
|
||||
)
|
||||
: null
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -228,39 +321,25 @@ class _ListItem extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => onChanged(!checkboxActive),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24,
|
||||
top: 4,
|
||||
right: 12,
|
||||
bottom: 4
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
),
|
||||
Checkbox(
|
||||
value: checkboxActive,
|
||||
onChanged: (v) => onChanged(!checkboxActive),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
final statusProvider = Provider.of<StatusProvider>(context);
|
||||
|
||||
String? name;
|
||||
try {
|
||||
name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(label)).name;
|
||||
} catch (e) {
|
||||
// ---- //
|
||||
}
|
||||
|
||||
return CustomCheckboxListTile(
|
||||
value: checkboxActive,
|
||||
onChanged: (v) => onChanged(v),
|
||||
title: label,
|
||||
subtitle: name,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24,
|
||||
top: 8,
|
||||
right: 12,
|
||||
bottom: 8
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import 'package:adguard_home_manager/constants/enums.dart';
|
|||
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/logs_provider.dart';
|
||||
|
||||
class LogsFiltersModal extends StatefulWidget {
|
||||
class LogsFiltersModal extends StatelessWidget {
|
||||
final bool dialog;
|
||||
|
||||
const LogsFiltersModal({
|
||||
|
@ -23,22 +23,9 @@ class LogsFiltersModal extends StatefulWidget {
|
|||
required this.dialog
|
||||
});
|
||||
|
||||
@override
|
||||
State<LogsFiltersModal> createState() => _LogsFiltersModalState();
|
||||
}
|
||||
|
||||
class _LogsFiltersModalState extends State<LogsFiltersModal> {
|
||||
TextEditingController searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
searchController.text = Provider.of<LogsProvider>(context, listen: false).searchText ?? '';
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.dialog == true) {
|
||||
if (dialog == true) {
|
||||
return Padding(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: Dialog(
|
||||
|
@ -46,10 +33,7 @@ class _LogsFiltersModalState extends State<LogsFiltersModal> {
|
|||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: _FiltersList(
|
||||
searchController: searchController,
|
||||
onClearSearch: () => setState(() => searchController.text = "")
|
||||
)
|
||||
child: const _FiltersList()
|
||||
)
|
||||
),
|
||||
);
|
||||
|
@ -65,11 +49,8 @@ class _LogsFiltersModalState extends State<LogsFiltersModal> {
|
|||
topRight: Radius.circular(28)
|
||||
)
|
||||
),
|
||||
child: SafeArea(
|
||||
child: _FiltersList(
|
||||
searchController: searchController,
|
||||
onClearSearch: () => setState(() => searchController.text = "")
|
||||
),
|
||||
child: const SafeArea(
|
||||
child: _FiltersList(),
|
||||
)
|
||||
),
|
||||
);
|
||||
|
@ -78,13 +59,7 @@ class _LogsFiltersModalState extends State<LogsFiltersModal> {
|
|||
}
|
||||
|
||||
class _FiltersList extends StatelessWidget {
|
||||
final TextEditingController searchController;
|
||||
final void Function() onClearSearch;
|
||||
|
||||
const _FiltersList({
|
||||
required this.searchController,
|
||||
required this.onClearSearch,
|
||||
});
|
||||
const _FiltersList();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -194,35 +169,6 @@ class _FiltersList extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: searchController,
|
||||
onChanged: logsProvider.setSearchText,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.search_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
labelText: AppLocalizations.of(context)!.search,
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
onClearSearch();
|
||||
logsProvider.setSearchText(null);
|
||||
},
|
||||
icon: const Icon(Icons.clear)
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(height: 16),
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.client,
|
||||
|
@ -300,7 +246,6 @@ class _FiltersList extends StatelessWidget {
|
|||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
searchController.text = "";
|
||||
logsProvider.requestResetFilters();
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!.resetFilters)
|
||||
|
|
|
@ -62,7 +62,7 @@ class LogsListAppBar extends StatelessWidget {
|
|||
"blocked_parental": AppLocalizations.of(context)!.blockedParentalRow,
|
||||
"safe_search": AppLocalizations.of(context)!.safeSearch,
|
||||
};
|
||||
|
||||
print(MediaQuery.of(context).viewPadding.top);
|
||||
return SliverAppBar.large(
|
||||
pinned: true,
|
||||
floating: true,
|
||||
|
@ -76,14 +76,32 @@ class LogsListAppBar extends StatelessWidget {
|
|||
icon: const Icon(Icons.refresh_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.refresh,
|
||||
),
|
||||
logsProvider.loadStatus == LoadStatus.loaded
|
||||
? IconButton(
|
||||
onPressed: openFilersModal,
|
||||
icon: const Icon(Icons.filter_list_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.filters,
|
||||
)
|
||||
: const SizedBox(),
|
||||
const SizedBox(width: 5),
|
||||
if (logsProvider.loadStatus == LoadStatus.loaded) IconButton(
|
||||
onPressed: () => showDialog(
|
||||
context: context,
|
||||
builder: (context) => _Search(
|
||||
hasTopBar: MediaQuery.of(context).viewPadding.top > 0,
|
||||
onSearch: (v) {
|
||||
logsProvider.setAppliedFilters(
|
||||
AppliedFiters(
|
||||
selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus,
|
||||
searchText: v != "" ? v : null,
|
||||
clients: logsProvider.appliedFilters.clients
|
||||
)
|
||||
);
|
||||
logsProvider.filterLogs();
|
||||
},
|
||||
),
|
||||
),
|
||||
icon: const Icon(Icons.search_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.search,
|
||||
),
|
||||
if (logsProvider.loadStatus == LoadStatus.loaded) IconButton(
|
||||
onPressed: openFilersModal,
|
||||
icon: const Icon(Icons.filter_list_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.filters,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
bottom: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients.isNotEmpty
|
||||
? PreferredSize(
|
||||
|
@ -213,4 +231,99 @@ class LogsListAppBar extends StatelessWidget {
|
|||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Search extends StatefulWidget {
|
||||
final void Function(String) onSearch;
|
||||
final bool hasTopBar;
|
||||
|
||||
const _Search({
|
||||
required this.onSearch,
|
||||
required this.hasTopBar,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_Search> createState() => _SearchState();
|
||||
}
|
||||
|
||||
class _SearchState extends State<_Search> {
|
||||
final _searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final logsProvider = Provider.of<LogsProvider>(context, listen: false);
|
||||
_searchController.text = logsProvider.appliedFilters.searchText ?? "";
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final logsProvider = Provider.of<LogsProvider>(context);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => {},
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 500),
|
||||
child: Container(
|
||||
margin: widget.hasTopBar
|
||||
? const EdgeInsets.symmetric(horizontal: 16)
|
||||
: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(16)
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: TextFormField(
|
||||
controller: _searchController,
|
||||
onChanged: (v) {
|
||||
if (v == "") {
|
||||
logsProvider.setSearchText(null);
|
||||
return;
|
||||
}
|
||||
logsProvider.setSearchText(v);
|
||||
},
|
||||
onFieldSubmitted: (v) {
|
||||
widget.onSearch(v);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(
|
||||
hintText: AppLocalizations.of(context)!.search,
|
||||
prefixIcon: const Icon(Icons.search_rounded),
|
||||
border: InputBorder.none,
|
||||
filled: true,
|
||||
fillColor: Colors.grey.withOpacity(0.2),
|
||||
suffixIcon: _searchController.text != ""
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
_searchController.text = "";
|
||||
logsProvider.setSearchText(null);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.close_rounded,
|
||||
size: 20,
|
||||
),
|
||||
tooltip: AppLocalizations.of(context)!.clearSearch,
|
||||
)
|
||||
: null
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -120,7 +120,7 @@ class _CustomizationWidgetState extends State<CustomizationWidget> {
|
|||
label: AppLocalizations.of(context)!.color,
|
||||
padding: const EdgeInsets.only(top: 45, left: 16, right: 16, bottom: 5),
|
||||
),
|
||||
if (appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31) CustomSwitchListTile(
|
||||
if (appConfigProvider.supportsDynamicTheme) CustomSwitchListTile(
|
||||
value: dynamicColor,
|
||||
onChanged: (value) {
|
||||
setState(() => dynamicColor = value);
|
||||
|
@ -129,7 +129,10 @@ class _CustomizationWidgetState extends State<CustomizationWidget> {
|
|||
title: AppLocalizations.of(context)!.useDynamicTheme,
|
||||
),
|
||||
if (!(appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31)) const SizedBox(height: 20),
|
||||
if (dynamicColor == false) Padding(
|
||||
if (
|
||||
appConfigProvider.supportsDynamicTheme == false ||
|
||||
(appConfigProvider.supportsDynamicTheme == true && dynamicColor == false)
|
||||
) Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8, left: 16, right: 16),
|
||||
child: Scrollbar(
|
||||
controller: _colorsScrollController,
|
||||
|
@ -207,15 +210,6 @@ class _CustomizationWidgetState extends State<CustomizationWidget> {
|
|||
),
|
||||
),
|
||||
),
|
||||
CustomSwitchListTile(
|
||||
value: useThemeColorInsteadGreenRed,
|
||||
onChanged: (value) {
|
||||
setState(() => useThemeColorInsteadGreenRed = value);
|
||||
appConfigProvider.setUseThemeColorForStatus(value);
|
||||
},
|
||||
title: AppLocalizations.of(context)!.useThemeColorStatus,
|
||||
subtitle: AppLocalizations.of(context)!.useThemeColorStatusDescription,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -5,6 +5,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/settings/dns/rate_limit_allowlist_modal.dart';
|
||||
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||
import 'package:adguard_home_manager/widgets/custom_radio_list_tile.dart';
|
||||
import 'package:adguard_home_manager/widgets/section_label.dart';
|
||||
import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart';
|
||||
|
@ -23,49 +25,50 @@ class DnsServerSettingsScreen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
||||
final TextEditingController limitRequestsController = TextEditingController();
|
||||
String? limitRequestsError;
|
||||
final _limitRequestsController = TextEditingController();
|
||||
final _ipv4PrefixSubnetController = TextEditingController();
|
||||
String? _ipv4PrefixSubnetError;
|
||||
final _ipv6PrefixSubnetController = TextEditingController();
|
||||
String? _ipv6PrefixSubnetError;
|
||||
List<String> _rateLimitAllowlist = [];
|
||||
String? _limitRequestsError;
|
||||
final _expandableCustomEdns = ExpandableController();
|
||||
final _expandableEdnsIp = ExpandableController();
|
||||
bool enableEdns = false;
|
||||
bool useCustomIpEdns = false;
|
||||
bool _enableEdns = false;
|
||||
bool _useCustomIpEdns = false;
|
||||
final _customIpEdnsController = TextEditingController();
|
||||
String? ednsIpError;
|
||||
bool enableDnssec = false;
|
||||
bool disableIpv6Resolving = false;
|
||||
String? _ednsIpError;
|
||||
bool _enableDnssec = false;
|
||||
bool _disableIpv6Resolving = false;
|
||||
|
||||
String blockingMode = "default";
|
||||
|
||||
final TextEditingController ipv4controller = TextEditingController();
|
||||
String? ipv4error;
|
||||
final TextEditingController ipv6controller = TextEditingController();
|
||||
final _ipv4controller = TextEditingController();
|
||||
String? _ipv4error;
|
||||
final _ipv6controller = TextEditingController();
|
||||
String? ipv6error;
|
||||
|
||||
final _ttlController = TextEditingController();
|
||||
String? _ttlError;
|
||||
|
||||
bool isDataValid = false;
|
||||
|
||||
void validateIpv4(String value) {
|
||||
RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$');
|
||||
if (ipAddress.hasMatch(value) == true) {
|
||||
setState(() => ipv4error = null);
|
||||
setState(() => _ipv4error = null);
|
||||
}
|
||||
else {
|
||||
setState(() => ipv4error = AppLocalizations.of(context)!.invalidIp);
|
||||
setState(() => _ipv4error = AppLocalizations.of(context)!.invalidIp);
|
||||
}
|
||||
validateData();
|
||||
}
|
||||
|
||||
void validateEdns(String value) {
|
||||
RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$');
|
||||
if (ipAddress.hasMatch(value) == true) {
|
||||
setState(() => ednsIpError = null);
|
||||
setState(() => _ednsIpError = null);
|
||||
}
|
||||
else {
|
||||
setState(() => ednsIpError = AppLocalizations.of(context)!.ipNotValid);
|
||||
setState(() => _ednsIpError = AppLocalizations.of(context)!.ipNotValid);
|
||||
}
|
||||
validateData();
|
||||
}
|
||||
|
||||
void validateIpv6(String value) {
|
||||
|
@ -76,63 +79,78 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
|||
else {
|
||||
setState(() => ipv6error = AppLocalizations.of(context)!.invalidIp);
|
||||
}
|
||||
validateData();
|
||||
}
|
||||
|
||||
void validateData() {
|
||||
bool validateData() {
|
||||
if (
|
||||
limitRequestsController.text != '' &&
|
||||
limitRequestsError == null &&
|
||||
(_limitRequestsController.text == "" || (_limitRequestsController.text != "" && _limitRequestsError == null)) &&
|
||||
(
|
||||
blockingMode != 'custom_ip' ||
|
||||
(
|
||||
blockingMode == 'custom_ip' &&
|
||||
ipv4controller.text != '' &&
|
||||
ipv4error == null &&
|
||||
ipv6controller.text != '' &&
|
||||
_ipv4controller.text != '' &&
|
||||
_ipv4error == null &&
|
||||
_ipv6controller.text != '' &&
|
||||
ipv6error == null
|
||||
)
|
||||
) == true &&
|
||||
ednsIpError == null &&
|
||||
_ttlError == null
|
||||
_ednsIpError == null &&
|
||||
_ttlController.text != "" && _ttlError == null &&
|
||||
(_ipv4PrefixSubnetController.text == "" || (_ipv4PrefixSubnetController.text != "" && _ipv4PrefixSubnetError == null)) &&
|
||||
(_ipv6PrefixSubnetController.text == "" || (_ipv6PrefixSubnetController.text != "" && _ipv6PrefixSubnetError == null))
|
||||
) {
|
||||
setState(() => isDataValid = true);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
setState(() => isDataValid = false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void validateNumber(String value) {
|
||||
final regex = RegExp(r'^(\d)+$');
|
||||
if (regex.hasMatch(value) == true) {
|
||||
setState(() => _ttlError = null);
|
||||
String? validateTtl(String value) {
|
||||
if (value == "") return AppLocalizations.of(context)!.valueNotNumber;
|
||||
if (int.tryParse(value) != null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
setState(() => _ttlError = AppLocalizations.of(context)!.invalidValue);
|
||||
return AppLocalizations.of(context)!.valueNotNumber;
|
||||
}
|
||||
}
|
||||
|
||||
String? validateNumber(String value) {
|
||||
if (value == "") return null;
|
||||
if (int.tryParse(value) != null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return AppLocalizations.of(context)!.valueNotNumber;
|
||||
}
|
||||
validateData();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final dnsProvider = Provider.of<DnsProvider>(context, listen: false);
|
||||
|
||||
limitRequestsController.text = dnsProvider.dnsInfo!.ratelimit.toString();
|
||||
enableEdns = dnsProvider.dnsInfo!.ednsCsEnabled;
|
||||
useCustomIpEdns = dnsProvider.dnsInfo!.ednsCsUseCustom ?? false;
|
||||
_limitRequestsController.text = dnsProvider.dnsInfo!.ratelimit.toString();
|
||||
_enableEdns = dnsProvider.dnsInfo!.ednsCsEnabled;
|
||||
_useCustomIpEdns = dnsProvider.dnsInfo!.ednsCsUseCustom ?? false;
|
||||
_customIpEdnsController.text = dnsProvider.dnsInfo!.ednsCsCustomIp ?? "";
|
||||
if (dnsProvider.dnsInfo!.ednsCsEnabled == true) _expandableCustomEdns.toggle();
|
||||
if (dnsProvider.dnsInfo!.ednsCsUseCustom == true) _expandableEdnsIp.toggle();
|
||||
enableDnssec = dnsProvider.dnsInfo!.dnssecEnabled;
|
||||
disableIpv6Resolving = dnsProvider.dnsInfo!.disableIpv6;
|
||||
_enableDnssec = dnsProvider.dnsInfo!.dnssecEnabled;
|
||||
_disableIpv6Resolving = dnsProvider.dnsInfo!.disableIpv6;
|
||||
blockingMode = dnsProvider.dnsInfo!.blockingMode;
|
||||
ipv4controller.text = dnsProvider.dnsInfo!.blockingIpv4;
|
||||
ipv6controller.text = dnsProvider.dnsInfo!.blockingIpv6;
|
||||
isDataValid = true;
|
||||
_ipv4controller.text = dnsProvider.dnsInfo!.blockingIpv4;
|
||||
_ipv6controller.text = dnsProvider.dnsInfo!.blockingIpv6;
|
||||
_ttlController.text = dnsProvider.dnsInfo!.blockedResponseTtl != null
|
||||
? dnsProvider.dnsInfo!.blockedResponseTtl.toString()
|
||||
: "";
|
||||
_ipv4PrefixSubnetController.text = dnsProvider.dnsInfo!.ratelimitSubnetLenIpv4 != null
|
||||
? dnsProvider.dnsInfo!.ratelimitSubnetLenIpv4.toString()
|
||||
: "";
|
||||
_ipv6PrefixSubnetController.text = dnsProvider.dnsInfo!.ratelimitSubnetLenIpv6 != null
|
||||
? dnsProvider.dnsInfo!.ratelimitSubnetLenIpv6.toString()
|
||||
: "";
|
||||
_rateLimitAllowlist = dnsProvider.dnsInfo!.ratelimitWhitelist ?? [];
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -147,16 +165,19 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
|||
processModal.open(AppLocalizations.of(context)!.savingConfig);
|
||||
|
||||
final result = await dnsProvider.saveDnsServerConfig({
|
||||
"ratelimit": int.parse(limitRequestsController.text),
|
||||
"edns_cs_enabled": enableEdns,
|
||||
"edns_cs_use_custom": useCustomIpEdns,
|
||||
"ratelimit": int.parse(_limitRequestsController.text),
|
||||
"edns_cs_enabled": _enableEdns,
|
||||
"edns_cs_use_custom": _useCustomIpEdns,
|
||||
"edns_cs_custom_ip": _customIpEdnsController.text,
|
||||
"dnssec_enabled": enableDnssec,
|
||||
"disable_ipv6": disableIpv6Resolving,
|
||||
"dnssec_enabled": _enableDnssec,
|
||||
"disable_ipv6": _disableIpv6Resolving,
|
||||
"blocking_mode": blockingMode,
|
||||
"blocking_ipv4": ipv4controller.text,
|
||||
"blocking_ipv6": ipv6controller.text,
|
||||
"blocked_response_ttl": int.tryParse(_ttlController.text)
|
||||
"blocking_ipv4": _ipv4controller.text,
|
||||
"blocking_ipv6": _ipv6controller.text,
|
||||
"blocked_response_ttl": int.tryParse(_ttlController.text),
|
||||
"ratelimit_subnet_len_ipv4": int.tryParse(_ipv4PrefixSubnetController.text),
|
||||
"ratelimit_subnet_len_ipv6": int.tryParse(_ipv6PrefixSubnetController.text),
|
||||
"ratelimit_whitelist": _rateLimitAllowlist
|
||||
});
|
||||
|
||||
processModal.close();
|
||||
|
@ -186,22 +207,24 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
|||
|
||||
void updateBlockingMode(String mode) {
|
||||
if (mode != 'custom_ip') {
|
||||
ipv4controller.text = '';
|
||||
ipv4error = null;
|
||||
ipv6controller.text = '';
|
||||
_ipv4controller.text = '';
|
||||
_ipv4error = null;
|
||||
_ipv6controller.text = '';
|
||||
ipv6error = null;
|
||||
}
|
||||
setState(() => blockingMode = mode);
|
||||
validateData();
|
||||
}
|
||||
|
||||
final dataValid = validateData();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.dnsServerSettings),
|
||||
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: isDataValid == true
|
||||
onPressed: dataValid == true
|
||||
? () => saveData()
|
||||
: null,
|
||||
icon: const Icon(Icons.save_rounded),
|
||||
|
@ -212,21 +235,17 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
|||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
padding: const EdgeInsets.only(top: 0),
|
||||
children: [
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.rateLimit,
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 24, top: 8),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 24, top: 4),
|
||||
child: TextFormField(
|
||||
controller: limitRequestsController,
|
||||
onChanged: (value) {
|
||||
if (int.tryParse(value) != null) {
|
||||
setState(() => limitRequestsError = null);
|
||||
}
|
||||
else {
|
||||
setState(() => limitRequestsError = AppLocalizations.of(context)!.valueNotNumber);
|
||||
}
|
||||
validateData();
|
||||
},
|
||||
controller: _limitRequestsController,
|
||||
onChanged: (v) => setState(() => _limitRequestsError = validateNumber(v)),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.looks_one_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
|
@ -235,22 +254,85 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
|||
)
|
||||
),
|
||||
labelText: AppLocalizations.of(context)!.limitRequestsSecond,
|
||||
errorText: limitRequestsError
|
||||
errorText: _limitRequestsError
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 24),
|
||||
child: TextFormField(
|
||||
controller: _ipv4PrefixSubnetController,
|
||||
onChanged: (v) => setState(() => _ipv4PrefixSubnetError = validateNumber(v)),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.skip_previous_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
labelText: AppLocalizations.of(context)!.subnetPrefixLengthIpv4,
|
||||
errorText: _ipv4PrefixSubnetError
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: TextFormField(
|
||||
controller: _ipv6PrefixSubnetController,
|
||||
onChanged: (v) => setState(() => _ipv6PrefixSubnetError = validateNumber(v)),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.skip_previous_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
labelText: AppLocalizations.of(context)!.subnetPrefixLengthIpv6,
|
||||
errorText: _ipv6PrefixSubnetError
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.rateLimitAllowlist,
|
||||
subtitle: AppLocalizations.of(context)!.rateLimitAllowlistDescription,
|
||||
onTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (context) => RateLimitAllowlistModal(
|
||||
values: _rateLimitAllowlist,
|
||||
onConfirm: (ips) => setState(() => _rateLimitAllowlist = ips)
|
||||
),
|
||||
),
|
||||
trailing: _rateLimitAllowlist.isNotEmpty ? Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(40)
|
||||
),
|
||||
child: Text(
|
||||
_rateLimitAllowlist.length.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer
|
||||
),
|
||||
),
|
||||
) : null,
|
||||
),
|
||||
SectionLabel(label: AppLocalizations.of(context)!.dnsOptions),
|
||||
CustomSwitchListTile(
|
||||
value: enableEdns,
|
||||
value: _enableEdns,
|
||||
onChanged: (value) => setState(() {
|
||||
enableEdns = value;
|
||||
_enableEdns = value;
|
||||
_expandableCustomEdns.toggle();
|
||||
if (value == false) {
|
||||
useCustomIpEdns = false;
|
||||
_useCustomIpEdns = false;
|
||||
if (_expandableEdnsIp.expanded == true) _expandableEdnsIp.toggle();
|
||||
_customIpEdnsController.text = "";
|
||||
ednsIpError = null;
|
||||
_ednsIpError = null;
|
||||
}
|
||||
validateData();
|
||||
}),
|
||||
|
@ -270,13 +352,13 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
|||
bottom: 12,
|
||||
right: 16
|
||||
),
|
||||
value: useCustomIpEdns,
|
||||
value: _useCustomIpEdns,
|
||||
onChanged: (value) => setState(() {
|
||||
useCustomIpEdns = value;
|
||||
_useCustomIpEdns = value;
|
||||
_expandableEdnsIp.toggle();
|
||||
if (useCustomIpEdns == false) {
|
||||
if (_useCustomIpEdns == false) {
|
||||
_customIpEdnsController.text = "";
|
||||
ednsIpError = null;
|
||||
_ednsIpError = null;
|
||||
}
|
||||
validateData();
|
||||
}),
|
||||
|
@ -304,7 +386,7 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
|||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
errorText: ednsIpError,
|
||||
errorText: _ednsIpError,
|
||||
labelText: AppLocalizations.of(context)!.ipAddress,
|
||||
),
|
||||
),
|
||||
|
@ -316,14 +398,14 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
|||
)
|
||||
),
|
||||
CustomSwitchListTile(
|
||||
value: enableDnssec,
|
||||
onChanged: (value) => setState(() => enableDnssec = value),
|
||||
value: _enableDnssec,
|
||||
onChanged: (value) => setState(() => _enableDnssec = value),
|
||||
title: AppLocalizations.of(context)!.enableDnssec,
|
||||
subtitle: AppLocalizations.of(context)!.enableDnssecDescription,
|
||||
),
|
||||
CustomSwitchListTile(
|
||||
value: disableIpv6Resolving,
|
||||
onChanged: (value) => setState(() => disableIpv6Resolving = value),
|
||||
value: _disableIpv6Resolving,
|
||||
onChanged: (value) => setState(() => _disableIpv6Resolving = value),
|
||||
title: AppLocalizations.of(context)!.disableResolvingIpv6,
|
||||
subtitle: AppLocalizations.of(context)!.disableResolvingIpv6Description,
|
||||
),
|
||||
|
@ -373,7 +455,7 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
|||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: TextFormField(
|
||||
controller: ipv4controller,
|
||||
controller: _ipv4controller,
|
||||
onChanged: validateIpv4,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.link_rounded),
|
||||
|
@ -382,7 +464,7 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
|||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
errorText: ipv4error,
|
||||
errorText: _ipv4error,
|
||||
helperText: AppLocalizations.of(context)!.blockingIpv4Description,
|
||||
helperMaxLines: 10,
|
||||
labelText: AppLocalizations.of(context)!.blockingIpv4,
|
||||
|
@ -394,7 +476,7 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
|||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: TextFormField(
|
||||
controller: ipv6controller,
|
||||
controller: _ipv6controller,
|
||||
onChanged: validateIpv6,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.link_rounded),
|
||||
|
@ -417,7 +499,7 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
|||
padding: const EdgeInsets.all(16),
|
||||
child: TextFormField(
|
||||
controller: _ttlController,
|
||||
onChanged: validateNumber,
|
||||
onChanged: (v) => setState(() => _ttlError = validateTtl(v)),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.timer_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
|
|
186
lib/screens/settings/dns/rate_limit_allowlist_modal.dart
Normal file
186
lib/screens/settings/dns/rate_limit_allowlist_modal.dart
Normal file
|
@ -0,0 +1,186 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class _IpListItemController {
|
||||
final String id;
|
||||
final TextEditingController controller;
|
||||
bool error;
|
||||
|
||||
_IpListItemController({
|
||||
required this.id,
|
||||
required this.controller,
|
||||
required this.error
|
||||
});
|
||||
}
|
||||
|
||||
class RateLimitAllowlistModal extends StatefulWidget {
|
||||
final List<String> values;
|
||||
final void Function(List<String>) onConfirm;
|
||||
|
||||
const RateLimitAllowlistModal({
|
||||
super.key,
|
||||
required this.values,
|
||||
required this.onConfirm,
|
||||
});
|
||||
|
||||
@override
|
||||
State<RateLimitAllowlistModal> createState() => _RateLimitAllowlistModalState();
|
||||
}
|
||||
|
||||
class _RateLimitAllowlistModalState extends State<RateLimitAllowlistModal> {
|
||||
final Uuid uuid = const Uuid();
|
||||
List<_IpListItemController> _controllersList = [];
|
||||
|
||||
void _validateIp(String value, _IpListItemController item) {
|
||||
final regexp = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$');
|
||||
if (regexp.hasMatch(value)) {
|
||||
setState(() => _controllersList = _controllersList.map((e) {
|
||||
if (e.id == item.id) {
|
||||
return _IpListItemController(
|
||||
id: e.id,
|
||||
controller: e.controller,
|
||||
error: false
|
||||
);
|
||||
}
|
||||
return e;
|
||||
}).toList());
|
||||
}
|
||||
else {
|
||||
setState(() => _controllersList = _controllersList.map((e) {
|
||||
if (e.id == item.id) {
|
||||
return _IpListItemController(
|
||||
id: e.id,
|
||||
controller: e.controller,
|
||||
error: true
|
||||
);
|
||||
}
|
||||
return e;
|
||||
}).toList());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_controllersList = widget.values.map((e) => _IpListItemController(
|
||||
id: uuid.v4(),
|
||||
controller: TextEditingController(text: e),
|
||||
error: false
|
||||
)).toList();
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final validData = _controllersList.where((e) => e.error == true).isEmpty;
|
||||
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 500),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Center(
|
||||
child: Icon(
|
||||
Icons.check_circle_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.rateLimitAllowlist,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
..._controllersList.map((item) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: item.controller,
|
||||
onChanged: (v) => _validateIp(v, item),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.link_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
labelText: AppLocalizations.of(context)!.ipAddress,
|
||||
errorText: item.error == true
|
||||
? AppLocalizations.of(context)!.invalidIp
|
||||
: null
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
IconButton(
|
||||
onPressed: () => setState(
|
||||
() => _controllersList = _controllersList.where((c) => c.id != item.id).toList()
|
||||
),
|
||||
icon: const Icon(Icons.remove_circle_outline_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.remove,
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
Center(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () => setState(() => _controllersList.add(
|
||||
_IpListItemController(
|
||||
id: uuid.v4(),
|
||||
controller: TextEditingController(),
|
||||
error: false
|
||||
),
|
||||
)),
|
||||
icon: const Icon(Icons.add_rounded),
|
||||
label: Text(AppLocalizations.of(context)!.addItem),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel)
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
TextButton(
|
||||
onPressed: validData == true
|
||||
? () {
|
||||
widget.onConfirm(
|
||||
_controllersList.map((e) => e.controller.text).toList()
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
: null,
|
||||
child: Text(AppLocalizations.of(context)!.confirm)
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/custom_checkbox_list_tile.dart';
|
||||
import 'package:adguard_home_manager/widgets/section_label.dart';
|
||||
import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart';
|
||||
import 'package:adguard_home_manager/screens/settings/encryption/status.dart';
|
||||
|
@ -35,6 +36,8 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
|
|||
|
||||
bool enabled = false;
|
||||
|
||||
bool? _plainDns;
|
||||
|
||||
final TextEditingController domainNameController = TextEditingController();
|
||||
String? domainError;
|
||||
|
||||
|
@ -70,7 +73,7 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
|
|||
|
||||
bool localValidationValid = false;
|
||||
String? validDataError;
|
||||
int certKeyValidApi = 0;
|
||||
bool _dataValidApi = true;
|
||||
|
||||
EncryptionValidation? certKeyValid;
|
||||
String? encryptionResultMessage;
|
||||
|
@ -112,6 +115,7 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
|
|||
privateKeyPathController.text = data.privateKeyPath;
|
||||
}
|
||||
usePreviouslySavedKey = data.privateKeySaved;
|
||||
_plainDns = data.servePlainDns;
|
||||
loadStatus = LoadStatus.loaded;
|
||||
});
|
||||
}
|
||||
|
@ -121,8 +125,6 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
|
|||
}
|
||||
|
||||
Future checkValidDataApi({Map<String, dynamic>? data}) async {
|
||||
setState(() => certKeyValidApi = 0);
|
||||
|
||||
final result = await Provider.of<ServersProvider>(context, listen: false).apiClient2!.checkEncryptionSettings(
|
||||
data: data ?? {
|
||||
"enabled": enabled,
|
||||
|
@ -146,11 +148,11 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
|
|||
final object = data.encryptionValidation!;
|
||||
setState(() {
|
||||
if (object.warningValidation != null && object.warningValidation != '') {
|
||||
certKeyValidApi = 2;
|
||||
_dataValidApi = false;
|
||||
validDataError = object.warningValidation;
|
||||
}
|
||||
else {
|
||||
certKeyValidApi = 1;
|
||||
_dataValidApi = true;
|
||||
validDataError = null;
|
||||
}
|
||||
certKeyValid = object;
|
||||
|
@ -159,7 +161,7 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
|
|||
else {
|
||||
setState(() {
|
||||
encryptionResultMessage = data.message;
|
||||
certKeyValidApi = 2;
|
||||
_dataValidApi = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -224,6 +226,7 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
|
|||
"private_key_saved": usePreviouslySavedKey,
|
||||
"certificate_path": certificatePathController.text,
|
||||
"private_key_path": privateKeyPathController.text,
|
||||
"serve_plain_dns": _plainDns
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -271,6 +274,7 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
|
|||
"private_key_saved": false,
|
||||
"certificate_path": "",
|
||||
"private_key_path": "",
|
||||
"serve_plain_dns": true
|
||||
}
|
||||
);
|
||||
if (!mounted) return;
|
||||
|
@ -345,7 +349,7 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
|
|||
case LoadStatus.loaded:
|
||||
return ListView(
|
||||
children: [
|
||||
if (certKeyValidApi == 2 && (validDataError != null || encryptionResultMessage != null)) Card(
|
||||
if (_dataValidApi == false && (validDataError != null || encryptionResultMessage != null)) Card(
|
||||
margin: const EdgeInsets.all(16),
|
||||
color: Colors.red.withOpacity(0.2),
|
||||
elevation: 0,
|
||||
|
@ -372,6 +376,16 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
|
|||
onEditValidate();
|
||||
}
|
||||
),
|
||||
if (_plainDns != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
CustomCheckboxListTile(
|
||||
value: _plainDns!,
|
||||
onChanged: (v) => setState(() => _plainDns = v),
|
||||
title: AppLocalizations.of(context)!.enablePlainDns,
|
||||
subtitle: AppLocalizations.of(context)!.enablePlainDnsDescription,
|
||||
disabled: enabled == false,
|
||||
),
|
||||
],
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.serverConfiguration,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
|
||||
|
|
|
@ -8,7 +8,7 @@ import 'package:provider/provider.dart';
|
|||
import 'package:store_checker/store_checker.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/settings/general_settings/reorderable_top_items_home.dart';
|
||||
import 'package:adguard_home_manager/screens/settings/general_settings/top_items_list/top_items_list_settings.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||
import 'package:adguard_home_manager/widgets/section_label.dart';
|
||||
|
@ -199,10 +199,10 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
title: AppLocalizations.of(context)!.topItemsOrder,
|
||||
subtitle: AppLocalizations.of(context)!.topItemsOrderDescription,
|
||||
onTap: () => widget.splitView == true
|
||||
? SplitView.of(context).push(const ReorderableTopItemsHome())
|
||||
? SplitView.of(context).push(const TopItemsListSettings())
|
||||
: Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ReorderableTopItemsHome()
|
||||
builder: (context) => const TopItemsListSettings()
|
||||
)
|
||||
)
|
||||
),
|
||||
|
|
|
@ -1,312 +0,0 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_reorderable_list/flutter_reorderable_list.dart' as reorderable_list;
|
||||
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 {
|
||||
final HomeTopItems title;
|
||||
final Key key;
|
||||
|
||||
const _ItemData({
|
||||
required this.title,
|
||||
required this.key
|
||||
});
|
||||
}
|
||||
|
||||
enum DraggingMode {
|
||||
iOS,
|
||||
android,
|
||||
}
|
||||
|
||||
class ReorderableTopItemsHome extends StatefulWidget {
|
||||
const ReorderableTopItemsHome({super.key});
|
||||
|
||||
@override
|
||||
State<ReorderableTopItemsHome> createState() => _ReorderableTopItemsHomeState();
|
||||
}
|
||||
|
||||
class _ReorderableTopItemsHomeState extends State<ReorderableTopItemsHome> {
|
||||
List<HomeTopItems> homeTopItemsList = [];
|
||||
List<HomeTopItems> persistHomeTopItemsList = [];
|
||||
List<_ItemData> renderItems = [];
|
||||
|
||||
int _indexOfKey(Key key) {
|
||||
return renderItems.indexWhere((_ItemData d) => d.key == key);
|
||||
}
|
||||
|
||||
bool _reorderCallback(Key item, Key newPosition) {
|
||||
int draggingIndex = _indexOfKey(item);
|
||||
int newPositionIndex = _indexOfKey(newPosition);
|
||||
|
||||
final draggedItem = renderItems[draggingIndex];
|
||||
|
||||
final List<HomeTopItems> reorderedItems = reorderEnumItems(draggingIndex, newPositionIndex);
|
||||
|
||||
setState(() {
|
||||
renderItems.removeAt(draggingIndex);
|
||||
renderItems.insert(newPositionIndex, draggedItem);
|
||||
homeTopItemsList = reorderedItems;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void _reorderDone(Key item) {
|
||||
renderItems[_indexOfKey(item)];
|
||||
setState(() => persistHomeTopItemsList = homeTopItemsList);
|
||||
}
|
||||
|
||||
List<HomeTopItems> reorderEnumItems(int oldIndex, int newIndex) {
|
||||
final List<HomeTopItems> list = [...homeTopItemsList];
|
||||
final HomeTopItems item = list.removeAt(oldIndex);
|
||||
list.insert(newIndex, item);
|
||||
return list;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context, listen: false);
|
||||
homeTopItemsList = appConfigProvider.homeTopItemsOrder;
|
||||
persistHomeTopItemsList = appConfigProvider.homeTopItemsOrder;
|
||||
renderItems = appConfigProvider.homeTopItemsOrder.asMap().entries.map(
|
||||
(e) => _ItemData(
|
||||
key: ValueKey(e.key),
|
||||
title: e.value,
|
||||
)
|
||||
).toList();
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
Widget tile(HomeTopItems title) {
|
||||
switch (title) {
|
||||
case HomeTopItems.queriedDomains:
|
||||
return CustomListTile(
|
||||
title: AppLocalizations.of(context)!.topQueriedDomains,
|
||||
icon: Icons.install_desktop_outlined,
|
||||
padding: const EdgeInsets.all(16)
|
||||
);
|
||||
|
||||
case HomeTopItems.blockedDomains:
|
||||
return CustomListTile(
|
||||
title: AppLocalizations.of(context)!.topBlockedDomains,
|
||||
icon: Icons.block_rounded,
|
||||
padding: const EdgeInsets.all(16)
|
||||
);
|
||||
|
||||
case HomeTopItems.recurrentClients:
|
||||
return CustomListTile(
|
||||
title: AppLocalizations.of(context)!.topClients,
|
||||
icon: Icons.smartphone_rounded,
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void saveSettings() async {
|
||||
final result = await appConfigProvider.setHomeTopItemsOrder(homeTopItemsList);
|
||||
if (!mounted) return;
|
||||
if (result == true) {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.settingsSaved,
|
||||
color: Colors.green
|
||||
);
|
||||
}
|
||||
else {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.settingsNotSaved,
|
||||
color: Colors.red
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final draggingMode = Platform.isAndroid
|
||||
? DraggingMode.android
|
||||
: DraggingMode.iOS;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.topItemsOrder),
|
||||
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: !listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList)
|
||||
? () => saveSettings()
|
||||
: null,
|
||||
icon: const Icon(Icons.save_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.save,
|
||||
),
|
||||
const SizedBox(width: 8)
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Card(
|
||||
margin: const EdgeInsets.all(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_rounded,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Flexible(
|
||||
child: Text(AppLocalizations.of(context)!.topItemsReorderInfo)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: reorderable_list.ReorderableList(
|
||||
onReorder: _reorderCallback,
|
||||
onReorderDone: _reorderDone,
|
||||
child: ListView.builder(
|
||||
itemBuilder: (context, index) => reorderable_list.ReorderableItem(
|
||||
key: renderItems[index].key,
|
||||
childBuilder: (context, state) {
|
||||
if (draggingMode == DraggingMode.android) {
|
||||
return reorderable_list.DelayedReorderableListener(
|
||||
child: _Tile(
|
||||
draggingMode: draggingMode,
|
||||
isFirst: index == 0,
|
||||
isLast: index == renderItems.length - 1,
|
||||
state: state,
|
||||
tileWidget: tile(renderItems[index].title),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return _Tile(
|
||||
draggingMode: draggingMode,
|
||||
isFirst: index == 0,
|
||||
isLast: index == renderItems.length - 1,
|
||||
state: state,
|
||||
tileWidget: tile(renderItems[index].title),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
itemCount: renderItems.length,
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Tile extends StatelessWidget {
|
||||
final Widget tileWidget;
|
||||
final bool isFirst;
|
||||
final bool isLast;
|
||||
final reorderable_list.ReorderableItemState state;
|
||||
final DraggingMode draggingMode;
|
||||
|
||||
const _Tile({
|
||||
required this.tileWidget,
|
||||
required this.isFirst,
|
||||
required this.isLast,
|
||||
required this.state,
|
||||
required this.draggingMode
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
BoxDecoration getDecoration() {
|
||||
if (
|
||||
state == reorderable_list.ReorderableItemState.dragProxy ||
|
||||
state == reorderable_list.ReorderableItemState.dragProxyFinished
|
||||
) {
|
||||
return BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface.withOpacity(0.7)
|
||||
);
|
||||
}
|
||||
else {
|
||||
bool placeholder = state == reorderable_list.ReorderableItemState.placeholder;
|
||||
return BoxDecoration(
|
||||
border: Border(
|
||||
top: isFirst && !placeholder ? BorderSide(
|
||||
width: 1,
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
||||
) : BorderSide.none,
|
||||
bottom: isLast && placeholder ? BorderSide.none : BorderSide(
|
||||
width: 1,
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: getDecoration(),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: Opacity(
|
||||
opacity: state == reorderable_list.ReorderableItemState.placeholder ? 0.0 : 1.0,
|
||||
child: IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: tileWidget
|
||||
),
|
||||
if (draggingMode == DraggingMode.iOS) reorderable_list.ReorderableListener(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.reorder,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_reorderable_list/flutter_reorderable_list.dart' as reorderable_list;
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
|
||||
class _ItemData {
|
||||
final HomeTopItems title;
|
||||
final Key key;
|
||||
|
||||
const _ItemData({
|
||||
required this.title,
|
||||
required this.key
|
||||
});
|
||||
}
|
||||
|
||||
enum DraggingMode {
|
||||
iOS,
|
||||
android,
|
||||
}
|
||||
|
||||
class ReorderableTopItemsHome extends StatefulWidget {
|
||||
final List<HomeTopItems> persistHomeTopItems;
|
||||
final void Function(List<HomeTopItems> value) setPersistHomeTopItems;
|
||||
|
||||
const ReorderableTopItemsHome({
|
||||
super.key,
|
||||
required this.persistHomeTopItems,
|
||||
required this.setPersistHomeTopItems,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ReorderableTopItemsHome> createState() => _ReorderableTopItemsHomeState();
|
||||
}
|
||||
|
||||
class _ReorderableTopItemsHomeState extends State<ReorderableTopItemsHome> {
|
||||
List<HomeTopItems> homeTopItemsList = [];
|
||||
List<_ItemData> renderItems = [];
|
||||
|
||||
int _indexOfKey(Key key) {
|
||||
return renderItems.indexWhere((_ItemData d) => d.key == key);
|
||||
}
|
||||
|
||||
bool _reorderCallback(Key item, Key newPosition) {
|
||||
int draggingIndex = _indexOfKey(item);
|
||||
int newPositionIndex = _indexOfKey(newPosition);
|
||||
|
||||
final draggedItem = renderItems[draggingIndex];
|
||||
|
||||
final List<HomeTopItems> reorderedItems = reorderEnumItems(draggingIndex, newPositionIndex);
|
||||
|
||||
setState(() {
|
||||
renderItems.removeAt(draggingIndex);
|
||||
renderItems.insert(newPositionIndex, draggedItem);
|
||||
homeTopItemsList = reorderedItems;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void _reorderDone(Key item) {
|
||||
renderItems[_indexOfKey(item)];
|
||||
widget.setPersistHomeTopItems(homeTopItemsList);
|
||||
}
|
||||
|
||||
List<HomeTopItems> reorderEnumItems(int oldIndex, int newIndex) {
|
||||
final List<HomeTopItems> list = [...homeTopItemsList];
|
||||
final HomeTopItems item = list.removeAt(oldIndex);
|
||||
list.insert(newIndex, item);
|
||||
return list;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
homeTopItemsList = widget.persistHomeTopItems;
|
||||
renderItems = widget.persistHomeTopItems.asMap().entries.map(
|
||||
(e) => _ItemData(
|
||||
key: ValueKey(e.key),
|
||||
title: e.value,
|
||||
)
|
||||
).toList();
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final draggingMode = Platform.isAndroid
|
||||
? DraggingMode.android
|
||||
: DraggingMode.iOS;
|
||||
|
||||
return SafeArea(
|
||||
top: false,
|
||||
bottom: true,
|
||||
child: Builder(
|
||||
builder: (context) => CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
SliverList.list(
|
||||
children: [
|
||||
Card(
|
||||
margin: const EdgeInsets.all(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_rounded,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Flexible(
|
||||
child: Text(AppLocalizations.of(context)!.topItemsReorderInfo)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (homeTopItemsList.isEmpty) Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.noElementsReorderMessage,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (homeTopItemsList.isNotEmpty) reorderable_list.ReorderableList(
|
||||
onReorder: _reorderCallback,
|
||||
onReorderDone: _reorderDone,
|
||||
child: ListView.builder(
|
||||
primary: false,
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.only(top: 0),
|
||||
itemBuilder: (context, index) => reorderable_list.ReorderableItem(
|
||||
key: renderItems[index].key,
|
||||
childBuilder: (context, state) {
|
||||
if (draggingMode == DraggingMode.android) {
|
||||
return reorderable_list.DelayedReorderableListener(
|
||||
child: _ReorderableTile(
|
||||
draggingMode: draggingMode,
|
||||
isFirst: index == 0,
|
||||
isLast: index == renderItems.length - 1,
|
||||
state: state,
|
||||
tileWidget: _TopItemTile(tile: renderItems[index].title),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return _ReorderableTile(
|
||||
draggingMode: draggingMode,
|
||||
isFirst: index == 0,
|
||||
isLast: index == renderItems.length - 1,
|
||||
state: state,
|
||||
tileWidget: _TopItemTile(tile: renderItems[index].title),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
itemCount: renderItems.length,
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TopItemTile extends StatelessWidget {
|
||||
final HomeTopItems tile;
|
||||
|
||||
const _TopItemTile({
|
||||
required this.tile,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
switch (tile) {
|
||||
case HomeTopItems.queriedDomains:
|
||||
return CustomListTile(
|
||||
title: AppLocalizations.of(context)!.topQueriedDomains,
|
||||
icon: Icons.install_desktop_outlined,
|
||||
padding: const EdgeInsets.all(16)
|
||||
);
|
||||
|
||||
case HomeTopItems.blockedDomains:
|
||||
return CustomListTile(
|
||||
title: AppLocalizations.of(context)!.topBlockedDomains,
|
||||
icon: Icons.block_rounded,
|
||||
padding: const EdgeInsets.all(16)
|
||||
);
|
||||
|
||||
case HomeTopItems.recurrentClients:
|
||||
return CustomListTile(
|
||||
title: AppLocalizations.of(context)!.topClients,
|
||||
icon: Icons.smartphone_rounded,
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _ReorderableTile extends StatelessWidget {
|
||||
final Widget tileWidget;
|
||||
final bool isFirst;
|
||||
final bool isLast;
|
||||
final reorderable_list.ReorderableItemState state;
|
||||
final DraggingMode draggingMode;
|
||||
|
||||
const _ReorderableTile({
|
||||
required this.tileWidget,
|
||||
required this.isFirst,
|
||||
required this.isLast,
|
||||
required this.state,
|
||||
required this.draggingMode
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
BoxDecoration getDecoration() {
|
||||
if (
|
||||
state == reorderable_list.ReorderableItemState.dragProxy ||
|
||||
state == reorderable_list.ReorderableItemState.dragProxyFinished
|
||||
) {
|
||||
return BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface.withOpacity(0.7)
|
||||
);
|
||||
}
|
||||
else {
|
||||
bool placeholder = state == reorderable_list.ReorderableItemState.placeholder;
|
||||
return BoxDecoration(
|
||||
border: Border(
|
||||
top: isFirst && !placeholder ? BorderSide(
|
||||
width: 1,
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
||||
) : BorderSide.none,
|
||||
bottom: isLast && placeholder ? BorderSide.none : BorderSide(
|
||||
width: 1,
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: getDecoration(),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: Opacity(
|
||||
opacity: state == reorderable_list.ReorderableItemState.placeholder ? 0.0 : 1.0,
|
||||
child: IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: tileWidget
|
||||
),
|
||||
if (draggingMode == DraggingMode.iOS) reorderable_list.ReorderableListener(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.reorder,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart';
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
|
||||
class ShowHideTopItemsList extends StatelessWidget {
|
||||
final List<HomeTopItems> enabledHomeTopItems;
|
||||
final void Function(List<HomeTopItems>) setEnabledHomeTopItems;
|
||||
|
||||
const ShowHideTopItemsList({
|
||||
super.key,
|
||||
required this.enabledHomeTopItems,
|
||||
required this.setEnabledHomeTopItems,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const padding = EdgeInsets.symmetric(horizontal: 16, vertical: 8);
|
||||
|
||||
void updateValue(HomeTopItems value, bool newStatus) {
|
||||
if (newStatus == true) {
|
||||
setEnabledHomeTopItems([
|
||||
...enabledHomeTopItems,
|
||||
value
|
||||
]);
|
||||
}
|
||||
else {
|
||||
setEnabledHomeTopItems(enabledHomeTopItems.where((e) => e != value).toList());
|
||||
}
|
||||
}
|
||||
|
||||
return SafeArea(
|
||||
top: false,
|
||||
bottom: true,
|
||||
child: Builder(
|
||||
builder: (context) => CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
SliverList.list(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
CustomSwitchListTile(
|
||||
value: enabledHomeTopItems.contains(HomeTopItems.queriedDomains),
|
||||
onChanged: (v) => updateValue(HomeTopItems.queriedDomains, v),
|
||||
title: AppLocalizations.of(context)!.topQueriedDomains,
|
||||
leadingIcon: Icons.install_desktop_outlined,
|
||||
padding: padding,
|
||||
),
|
||||
CustomSwitchListTile(
|
||||
value: enabledHomeTopItems.contains(HomeTopItems.blockedDomains),
|
||||
onChanged: (v) => updateValue(HomeTopItems.blockedDomains, v),
|
||||
title: AppLocalizations.of(context)!.topBlockedDomains,
|
||||
leadingIcon: Icons.block_rounded,
|
||||
padding: padding,
|
||||
),
|
||||
CustomSwitchListTile(
|
||||
value: enabledHomeTopItems.contains(HomeTopItems.recurrentClients),
|
||||
onChanged: (v) => updateValue(HomeTopItems.recurrentClients, v),
|
||||
title: AppLocalizations.of(context)!.topClients,
|
||||
leadingIcon: Icons.smartphone_rounded,
|
||||
padding: padding,
|
||||
),
|
||||
CustomSwitchListTile(
|
||||
value: enabledHomeTopItems.contains(HomeTopItems.topUpstreams),
|
||||
onChanged: (v) => updateValue(HomeTopItems.topUpstreams, v),
|
||||
title: AppLocalizations.of(context)!.topUpstreams,
|
||||
leadingIcon: Icons.upload_file_rounded,
|
||||
padding: padding,
|
||||
),
|
||||
CustomSwitchListTile(
|
||||
value: enabledHomeTopItems.contains(HomeTopItems.avgUpstreamResponseTime),
|
||||
onChanged: (v) => updateValue(HomeTopItems.avgUpstreamResponseTime, v),
|
||||
title: AppLocalizations.of(context)!.averageUpstreamResponseTime,
|
||||
leadingIcon: Icons.timer_rounded,
|
||||
padding: padding,
|
||||
),
|
||||
]
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/settings/general_settings/top_items_list/show_hide_top_items_list.dart';
|
||||
import 'package:adguard_home_manager/screens/settings/general_settings/top_items_list/reorderable_top_items_home.dart';
|
||||
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/functions/desktop_mode.dart';
|
||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
|
||||
class TopItemsListSettings extends StatefulWidget {
|
||||
const TopItemsListSettings({super.key});
|
||||
|
||||
@override
|
||||
State<TopItemsListSettings> createState() => _TopItemsListSettingsState();
|
||||
}
|
||||
|
||||
class _TopItemsListSettingsState extends State<TopItemsListSettings> with TickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
|
||||
List<HomeTopItems> persistHomeTopItemsList = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context, listen: false);
|
||||
persistHomeTopItemsList = appConfigProvider.homeTopItemsOrder;
|
||||
|
||||
super.initState();
|
||||
|
||||
_tabController = TabController(length: 2, vsync: this);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void saveSettings() async {
|
||||
final result = await appConfigProvider.setHomeTopItemsOrder(persistHomeTopItemsList);
|
||||
if (!context.mounted) return;
|
||||
if (result == true) {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.settingsSaved,
|
||||
color: Colors.green
|
||||
);
|
||||
}
|
||||
else {
|
||||
showSnacbkar(
|
||||
appConfigProvider: appConfigProvider,
|
||||
label: AppLocalizations.of(context)!.settingsNotSaved,
|
||||
color: Colors.red
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: DefaultTabController(
|
||||
length: 2,
|
||||
child: NestedScrollView(
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: SliverAppBar(
|
||||
pinned: true,
|
||||
floating: true,
|
||||
centerTitle: false,
|
||||
forceElevated: innerBoxIsScrolled,
|
||||
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||
title: Text(AppLocalizations.of(context)!.topItemsOrder),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: !listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList)
|
||||
? () => saveSettings()
|
||||
: null,
|
||||
icon: const Icon(Icons.save_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.save,
|
||||
),
|
||||
const SizedBox(width: 8)
|
||||
],
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.reorder_rounded),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.reorder)
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.remove_red_eye_rounded),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.showHide)
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
)
|
||||
],
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
ReorderableTopItemsHome(
|
||||
persistHomeTopItems: persistHomeTopItemsList,
|
||||
setPersistHomeTopItems: (v) => setState(() => persistHomeTopItemsList = v),
|
||||
),
|
||||
ShowHideTopItemsList(
|
||||
enabledHomeTopItems: persistHomeTopItemsList,
|
||||
setEnabledHomeTopItems: (v) => setState(() => persistHomeTopItemsList = v),
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -92,7 +92,7 @@ class LogsConfigOptions extends StatelessWidget {
|
|||
child: Text(dropdownItemTranslation[item.key]),
|
||||
)).toList(),
|
||||
value: retentionTime,
|
||||
onChanged: (value) => updateRetentionTime(value as double),
|
||||
onChanged: (value) => updateRetentionTime(double.tryParse(value.toString())),
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
|
|
|
@ -1,141 +1,7 @@
|
|||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
import 'package:adguard_home_manager/config/home_top_items_default_order.dart';
|
||||
|
||||
Future<Map<String, dynamic>> loadDb(bool acceptsDynamicTheme) async {
|
||||
Future<Map<String, dynamic>> loadDb() async {
|
||||
List<Map<String, Object?>>? servers;
|
||||
List<Map<String, Object?>>? appConfig;
|
||||
|
||||
Future rebuildAppConfig(Database db) async {
|
||||
await db.execute("DROP TABLE appConfig");
|
||||
await db.execute("CREATE TABLE appConfig (theme NUMERIC, overrideSslCheck NUMERIC, hideZeroValues NUMERIC, useDynamicColor NUMERIC, staticColor NUMERIC, useThemeColorForStatus NUMERIC, showTimeLogs NUMERIC, showIpLogs NUMERIC, combinedChart NUMERIC, doNotRememberVersion TEXT)");
|
||||
await db.execute("INSERT INTO appConfig (theme, overrideSslCheck, hideZeroValues, useDynamicColor, staticColor, useThemeColorForStatus, showTimeLogs, showIpLogs, combinedChart) VALUES (0, 0, 0, ${acceptsDynamicTheme == true ? 1 : 0}, 0, 0, 0, 0, 0)");
|
||||
}
|
||||
|
||||
Future upgradeDbToV2(Database db) async {
|
||||
await db.execute("ALTER TABLE appConfig ADD COLUMN overrideSslCheck NUMERIC");
|
||||
await db.execute("UPDATE appConfig SET overrideSslCheck = 0");
|
||||
|
||||
await db.transaction((txn) async{
|
||||
await txn.rawQuery(
|
||||
'SELECT * FROM appConfig',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future upgradeDbToV3(Database db) async {
|
||||
await db.execute("ALTER TABLE appConfig ADD COLUMN hideZeroValues NUMERIC");
|
||||
await db.execute("UPDATE appConfig SET hideZeroValues = 0");
|
||||
|
||||
await db.transaction((txn) async{
|
||||
await txn.rawQuery(
|
||||
'SELECT * FROM appConfig',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future upgradeDbToV4(Database db) async {
|
||||
await db.execute("ALTER TABLE servers ADD COLUMN runningOnHa INTEGER");
|
||||
await db.execute("UPDATE servers SET runningOnHa = 0");
|
||||
|
||||
await db.transaction((txn) async{
|
||||
await txn.rawQuery(
|
||||
'SELECT * FROM servers',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future upgradeDbToV5(Database db) async {
|
||||
await db.execute("ALTER TABLE appConfig ADD COLUMN useDynamicColor NUMERIC");
|
||||
await db.execute("ALTER TABLE appConfig ADD COLUMN staticColor NUMERIC");
|
||||
await db.execute("ALTER TABLE appConfig ADD COLUMN useThemeColorForStatus NUMERIC");
|
||||
await db.execute("UPDATE appConfig SET useDynamicColor = ${acceptsDynamicTheme == true ? 1 : 0}, staticColor = 0, useThemeColorForStatus = 0");
|
||||
|
||||
await db.transaction((txn) async{
|
||||
await txn.rawQuery(
|
||||
'SELECT * FROM appConfig',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future upgradeDbToV6(Database db) async {
|
||||
await db.execute("ALTER TABLE appConfig ADD COLUMN showNameTimeLogs NUMERIC");
|
||||
await db.execute("UPDATE appConfig SET showNameTimeLogs = 0");
|
||||
|
||||
await db.transaction((txn) async{
|
||||
await txn.rawQuery(
|
||||
'SELECT * FROM appConfig',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future upgradeDbToV7(Database db) async {
|
||||
await db.execute("ALTER TABLE appConfig ADD COLUMN doNotRememberVersion TEXT");
|
||||
|
||||
await db.transaction((txn) async{
|
||||
await txn.rawQuery(
|
||||
'SELECT * FROM appConfig',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future upgradeDbToV8(Database db) async {
|
||||
try {
|
||||
final data = await db.rawQuery(
|
||||
'SELECT * FROM appConfig',
|
||||
);
|
||||
await rebuildAppConfig(db);
|
||||
await db.update(
|
||||
'appConfig',
|
||||
{
|
||||
'theme': data[0]['theme'],
|
||||
'overrideSslCheck': data[0]['overrideSslCheck'],
|
||||
'hideZeroValues': data[0]['hideZeroValues'],
|
||||
'useDynamicColor': data[0]['useDynamicColor'],
|
||||
'staticColor': data[0]['staticColor'],
|
||||
'useThemeColorForStatus': data[0]['useThemeColorForStatus'],
|
||||
'showTimeLogs': data[0]['showNameTimeLogs'],
|
||||
'showIpLogs': data[0]['showIpLogs'],
|
||||
'combinedChart': data[0]['combinedChart'],
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
await rebuildAppConfig(db);
|
||||
}
|
||||
}
|
||||
|
||||
Future upgradeDbToV9(Database db) async {
|
||||
await db.execute("ALTER TABLE appConfig ADD COLUMN hideServerAddress NUMERIC");
|
||||
await db.execute("ALTER TABLE appConfig ADD COLUMN homeTopItemsOrder TEXT");
|
||||
await db.execute("UPDATE appConfig SET hideServerAddress = 0, homeTopItemsOrder = '$homeTopItemsDefaultOrderString'");
|
||||
|
||||
await db.transaction((txn) async{
|
||||
await txn.rawQuery(
|
||||
'SELECT * FROM appConfig',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future upgradeDbToV10(Database db) async {
|
||||
await db.execute("ALTER TABLE appConfig ADD COLUMN showTopItemsChart NUMERIC");
|
||||
await db.execute("UPDATE appConfig SET showTopItemsChart = 1");
|
||||
|
||||
await db.transaction((txn) async{
|
||||
await txn.rawQuery(
|
||||
'SELECT * FROM appConfig',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future upgradeDbToV11(Database db) async {
|
||||
await db.execute("ALTER TABLE appConfig DROP COLUMN showTopItemsChart");
|
||||
|
||||
await db.transaction((txn) async{
|
||||
await txn.rawQuery(
|
||||
'SELECT * FROM appConfig',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Database db = await openDatabase(
|
||||
'adguard_home_manager.db',
|
||||
|
@ -159,135 +25,6 @@ Future<Map<String, dynamic>> loadDb(bool acceptsDynamicTheme) async {
|
|||
)
|
||||
"""
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
"""
|
||||
CREATE TABLE
|
||||
appConfig (
|
||||
theme NUMERIC,
|
||||
overrideSslCheck NUMERIC,
|
||||
hideZeroValues NUMERIC,
|
||||
useDynamicColor NUMERIC,
|
||||
staticColor NUMERIC,
|
||||
useThemeColorForStatus NUMERIC,
|
||||
showTimeLogs NUMERIC,
|
||||
showIpLogs NUMERIC,
|
||||
combinedChart NUMERIC,
|
||||
doNotRememberVersion TEXT,
|
||||
hideServerAddress NUMERIC,
|
||||
homeTopItemsOrder TEXT
|
||||
)
|
||||
"""
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
"""
|
||||
INSERT INTO
|
||||
appConfig (
|
||||
theme,
|
||||
overrideSslCheck,
|
||||
hideZeroValues,
|
||||
useDynamicColor,
|
||||
staticColor,
|
||||
useThemeColorForStatus,
|
||||
showTimeLogs,
|
||||
showIpLogs,
|
||||
combinedChart,
|
||||
hideServerAddress,
|
||||
homeTopItemsOrder
|
||||
)
|
||||
VALUES (
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
${acceptsDynamicTheme == true ? 1 : 0},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
'$homeTopItemsDefaultOrderString'
|
||||
)
|
||||
"""
|
||||
);
|
||||
},
|
||||
onUpgrade: (Database db, int oldVersion, int newVersion) async {
|
||||
if (oldVersion == 1) {
|
||||
await upgradeDbToV2(db);
|
||||
await upgradeDbToV3(db);
|
||||
await upgradeDbToV4(db);
|
||||
await upgradeDbToV5(db);
|
||||
await upgradeDbToV6(db);
|
||||
await upgradeDbToV7(db);
|
||||
await upgradeDbToV8(db);
|
||||
await upgradeDbToV9(db);
|
||||
await upgradeDbToV10(db);
|
||||
await upgradeDbToV11(db);
|
||||
}
|
||||
if (oldVersion == 2) {
|
||||
await upgradeDbToV3(db);
|
||||
await upgradeDbToV4(db);
|
||||
await upgradeDbToV5(db);
|
||||
await upgradeDbToV6(db);
|
||||
await upgradeDbToV7(db);
|
||||
await upgradeDbToV8(db);
|
||||
await upgradeDbToV9(db);
|
||||
await upgradeDbToV10(db);
|
||||
await upgradeDbToV11(db);
|
||||
}
|
||||
if (oldVersion == 3) {
|
||||
await upgradeDbToV4(db);
|
||||
await upgradeDbToV5(db);
|
||||
await upgradeDbToV6(db);
|
||||
await upgradeDbToV7(db);
|
||||
await upgradeDbToV8(db);
|
||||
await upgradeDbToV9(db);
|
||||
await upgradeDbToV10(db);
|
||||
await upgradeDbToV11(db);
|
||||
}
|
||||
if (oldVersion == 4) {
|
||||
await upgradeDbToV5(db);
|
||||
await upgradeDbToV6(db);
|
||||
await upgradeDbToV7(db);
|
||||
await upgradeDbToV8(db);
|
||||
await upgradeDbToV9(db);
|
||||
await upgradeDbToV10(db);
|
||||
await upgradeDbToV11(db);
|
||||
}
|
||||
if (oldVersion == 5) {
|
||||
await upgradeDbToV6(db);
|
||||
await upgradeDbToV7(db);
|
||||
await upgradeDbToV8(db);
|
||||
await upgradeDbToV9(db);
|
||||
await upgradeDbToV10(db);
|
||||
await upgradeDbToV11(db);
|
||||
}
|
||||
if (oldVersion == 6) {
|
||||
await upgradeDbToV7(db);
|
||||
await upgradeDbToV8(db);
|
||||
await upgradeDbToV9(db);
|
||||
await upgradeDbToV10(db);
|
||||
await upgradeDbToV11(db);
|
||||
}
|
||||
if (oldVersion == 7) {
|
||||
await upgradeDbToV8(db);
|
||||
await upgradeDbToV9(db);
|
||||
await upgradeDbToV10(db);
|
||||
await upgradeDbToV11(db);
|
||||
}
|
||||
if (oldVersion == 8) {
|
||||
await upgradeDbToV9(db);
|
||||
await upgradeDbToV10(db);
|
||||
await upgradeDbToV11(db);
|
||||
}
|
||||
if (oldVersion == 9) {
|
||||
await upgradeDbToV10(db);
|
||||
await upgradeDbToV11(db);
|
||||
}
|
||||
if (oldVersion == 10) {
|
||||
await upgradeDbToV11(db);
|
||||
}
|
||||
},
|
||||
onOpen: (Database db) async {
|
||||
await db.transaction((txn) async{
|
||||
|
@ -295,17 +32,11 @@ Future<Map<String, dynamic>> loadDb(bool acceptsDynamicTheme) async {
|
|||
'SELECT * FROM servers',
|
||||
);
|
||||
});
|
||||
await db.transaction((txn) async{
|
||||
appConfig = await txn.rawQuery(
|
||||
'SELECT * FROM appConfig',
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
"servers": servers,
|
||||
"appConfig": appConfig![0],
|
||||
"dbInstance": db,
|
||||
};
|
||||
}
|
|
@ -7,16 +7,18 @@ class CustomSwitchListTile extends StatelessWidget {
|
|||
final String? subtitle;
|
||||
final bool? disabled;
|
||||
final EdgeInsets? padding;
|
||||
final IconData? leadingIcon;
|
||||
|
||||
const CustomSwitchListTile({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
required this.title,
|
||||
this.disabled,
|
||||
this.subtitle,
|
||||
this.padding
|
||||
}) : super(key: key);
|
||||
this.padding,
|
||||
this.leadingIcon,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -33,6 +35,13 @@ class CustomSwitchListTile extends StatelessWidget {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (leadingIcon != null) ...[
|
||||
Icon(
|
||||
leadingIcon,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
|
|
@ -20,67 +20,64 @@ class ListBottomSheet extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: DraggableScrollableSheet(
|
||||
initialChildSize: initialChildSize ?? 0.6,
|
||||
minChildSize: minChildSize ?? 0.3,
|
||||
maxChildSize: maxChildSize ?? 1,
|
||||
builder: (context, controller) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
topRight: Radius.circular(28),
|
||||
),
|
||||
return DraggableScrollableSheet(
|
||||
initialChildSize: initialChildSize ?? 0.6,
|
||||
minChildSize: minChildSize ?? 0.3,
|
||||
maxChildSize: maxChildSize ?? 1,
|
||||
builder: (context, controller) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
topRight: Radius.circular(28),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: ListView(
|
||||
controller: controller,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
),
|
||||
child: SafeArea(
|
||||
child: ListView(
|
||||
controller: controller,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
width: 36,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Colors.grey
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
width: 36,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Colors.grey
|
||||
Icon(
|
||||
icon,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
...children
|
||||
],
|
||||
),
|
||||
),
|
||||
...children
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ class CustomTabContentList extends StatelessWidget {
|
|||
final bool? fabVisible;
|
||||
final bool? noSliver;
|
||||
final EdgeInsets? listPadding;
|
||||
final double? heightFabHidden;
|
||||
|
||||
const CustomTabContentList({
|
||||
super.key,
|
||||
|
@ -31,7 +32,8 @@ class CustomTabContentList extends StatelessWidget {
|
|||
this.fab,
|
||||
this.fabVisible,
|
||||
this.noSliver,
|
||||
this.listPadding
|
||||
this.listPadding,
|
||||
this.heightFabHidden,
|
||||
});
|
||||
|
||||
@override
|
||||
|
@ -150,7 +152,7 @@ class CustomTabContentList extends StatelessWidget {
|
|||
bottom: fabVisible != null && fabVisible == true ?
|
||||
appConfigProvider.showingSnackbar
|
||||
? 90 : 20
|
||||
: -90,
|
||||
: (heightFabHidden ?? -90),
|
||||
right: 20,
|
||||
child: SafeArea(child: fab!)
|
||||
),
|
||||
|
|
|
@ -9,6 +9,7 @@ import device_info_plus
|
|||
import dynamic_color
|
||||
import package_info_plus
|
||||
import sentry_flutter
|
||||
import shared_preferences_foundation
|
||||
import sqflite
|
||||
import sqlite3_flutter_libs
|
||||
import url_launcher_macos
|
||||
|
@ -19,6 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
|
|
|
@ -6,28 +6,31 @@ PODS:
|
|||
- FlutterMacOS (1.0.0)
|
||||
- package_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- Sentry/HybridSDK (8.18.0):
|
||||
- SentryPrivate (= 8.18.0)
|
||||
- Sentry/HybridSDK (8.19.0):
|
||||
- SentryPrivate (= 8.19.0)
|
||||
- sentry_flutter (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- Sentry/HybridSDK (= 8.18.0)
|
||||
- SentryPrivate (8.18.0)
|
||||
- Sentry/HybridSDK (= 8.19.0)
|
||||
- SentryPrivate (8.19.0)
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqflite (0.0.3):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqlite3 (3.45.0):
|
||||
- sqlite3/common (= 3.45.0)
|
||||
- sqlite3/common (3.45.0)
|
||||
- sqlite3/fts5 (3.45.0):
|
||||
- sqlite3 (3.45.1):
|
||||
- sqlite3/common (= 3.45.1)
|
||||
- sqlite3/common (3.45.1)
|
||||
- sqlite3/fts5 (3.45.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/perf-threadsafe (3.45.0):
|
||||
- sqlite3/perf-threadsafe (3.45.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.45.0):
|
||||
- sqlite3/rtree (3.45.1):
|
||||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- FlutterMacOS
|
||||
- sqlite3 (~> 3.45.0)
|
||||
- sqlite3 (~> 3.45.1)
|
||||
- sqlite3/fts5
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
|
@ -42,6 +45,7 @@ DEPENDENCIES:
|
|||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||
- sentry_flutter (from `Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos`)
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`)
|
||||
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
|
@ -64,6 +68,8 @@ EXTERNAL SOURCES:
|
|||
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
||||
sentry_flutter:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos
|
||||
shared_preferences_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||
sqflite:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin
|
||||
sqlite3_flutter_libs:
|
||||
|
@ -78,12 +84,13 @@ SPEC CHECKSUMS:
|
|||
dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
|
||||
Sentry: 8984a4ffb2b9bd2894d74fb36e6f5833865bc18e
|
||||
sentry_flutter: c87a0556eeb6cbf7f9f924d30e878bdedf22d364
|
||||
SentryPrivate: 2f0c9ba4c3fc993f70eab6ca95673509561e0085
|
||||
Sentry: 1ebcaef678a27c8ac515f974cb5425dd1bbdec2f
|
||||
sentry_flutter: ecdfbedee55337205561cfa782ee02d31ec83e1f
|
||||
SentryPrivate: 765c9b4ebe9ac1a5fcdc067c5a1cfbf3f10e1677
|
||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
sqlite3: f307b6291c4db7b5086c38d6237446b98a738581
|
||||
sqlite3_flutter_libs: 6b9913d8fbb718e5ebf23658aa6934a0fb509c0f
|
||||
sqlite3: 73b7fc691fdc43277614250e04d183740cb15078
|
||||
sqlite3_flutter_libs: 06a05802529659a272beac4ee1350bfec294f386
|
||||
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
|
||||
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195
|
||||
|
||||
|
|
132
pubspec.lock
132
pubspec.lock
|
@ -141,10 +141,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: "0042cb3b2a76413ea5f8a2b40cec2a33e01d0c937e91f0f7c211fde4f7739ba6"
|
||||
sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.1"
|
||||
version: "9.1.2"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -386,10 +386,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "004a2e90ce080f8627b5a04aecb4cdfac87d2c3f3b520aa291260be5a32c033d"
|
||||
sha256: "49a0d4b0c12402853d3f227fe7c315601b238d126aa4caa5dbb2dcf99421aa4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.4"
|
||||
version: "4.1.6"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -502,6 +502,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
percent_indicator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -526,6 +550,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -562,18 +594,74 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: sentry
|
||||
sha256: "5686ed515bb620dc52b4ae99a6586fe720d443591183cf1f620ec5d1f0eec100"
|
||||
sha256: a7946f4a90b0feb47214981d881b98149e05f6c576da9f2a2f33945bf561de25
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.15.0"
|
||||
version: "7.16.0"
|
||||
sentry_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sentry_flutter
|
||||
sha256: "505dec3b6810562785d2c34ae871c73ff2cba6cf436c32c188f0464df226ba8f"
|
||||
sha256: "6db7fa1b076faf2f5dd77d8cc9ef206171f32a290cc638842d78e5d62b441a27"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.15.0"
|
||||
version: "7.16.0"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.5"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -615,10 +703,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite_common_ffi
|
||||
sha256: d0e3f0d04fdf668e57db8db1df758f56c4193cb429092c708e7bfcc6ab04b27e
|
||||
sha256: "754927d82de369a6b9e760fb60640aa81da650f35ffd468d5a992814d6022908"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
version: "2.3.2+1"
|
||||
sqlite3:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -631,10 +719,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: sqlite3_flutter_libs
|
||||
sha256: "90963b515721d6a71e96f438175cf43c979493ed14822860a300b69694c74eb6"
|
||||
sha256: d6c31c8511c441d1f12f20b607343df1afe4eddf24a1cf85021677c8eea26060
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.19+1"
|
||||
version: "0.5.20"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -791,26 +879,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics
|
||||
sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172"
|
||||
sha256: "4ac59808bbfca6da38c99f415ff2d3a5d7ca0a6b4809c71d9cf30fba5daf9752"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.9+2"
|
||||
version: "1.1.10+1"
|
||||
vector_graphics_codec:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_codec
|
||||
sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d"
|
||||
sha256: f3247e7ab0ec77dc759263e68394990edc608fb2b480b80db8aa86ed09279e33
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.9+2"
|
||||
version: "1.1.10+1"
|
||||
vector_graphics_compiler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad"
|
||||
sha256: "18489bdd8850de3dd7ca8a34e0c446f719ec63e2bab2e7a8cc66a9028dd76c5a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.9+2"
|
||||
version: "1.1.10+1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -852,6 +940,14 @@ packages:
|
|||
url: "https://github.com/google/flutter-desktop-embedding"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -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.15.1+126
|
||||
version: 2.16.0+130
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.1 <3.0.0'
|
||||
|
@ -77,6 +77,7 @@ dependencies:
|
|||
timezone: ^0.9.2
|
||||
flutter_custom_tabs: ^2.0.0+1
|
||||
url_launcher: ^6.2.4
|
||||
shared_preferences: ^2.2.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Add table
Reference in a new issue