Merge branch 'beta'

This commit is contained in:
Juan Gilsanz Polo 2024-03-10 20:31:36 +01:00
commit ecc9cf1073
19 changed files with 926 additions and 522 deletions

View file

@ -779,5 +779,17 @@
"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.", "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.",
"date": "Date", "date": "Date",
"loadingChangelog": "Loading changelog...", "loadingChangelog": "Loading changelog...",
"invalidIpOrUrl": "Invalid IP address or URL" "invalidIpOrUrl": "Invalid IP address or URL",
"addPersistentClient": "Add as a persistent client",
"blockThisClientOnly": "Block for this client only",
"unblockThisClientOnly": "Unblock for this client only",
"domainBlockedThisClient": "{domain} blocked for this client",
"domainUnblockedThisClient": "{domain} unblocked for this client",
"disallowThisClient": "Disallow this client",
"allowThisClient": "Allow this client",
"clientAllowedSuccessfully": "Client allowed successfully",
"clientDisallowedSuccessfully": "Client disallowed successfully",
"changesNotSaved": "Changes could not be saved",
"allowingClient": "Allowing client...",
"disallowingClient": "Disallowing client..."
} }

View file

@ -779,5 +779,17 @@
"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.", "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.",
"date": "Fecha", "date": "Fecha",
"loadingChangelog": "Cargando registro de cambios...", "loadingChangelog": "Cargando registro de cambios...",
"invalidIpOrUrl": "Dirección IP o URL no válida" "invalidIpOrUrl": "Dirección IP o URL no válida",
"addPersistentClient": "Añadir como cliente persistente",
"blockThisClientOnly": "Bloquear sólo para este cliente",
"unblockThisClientOnly": "Desbloquear sólo para este cliente",
"domainBlockedThisClient": "{domain} bloqueado para este cliente",
"domainUnblockedThisClient": "{domain} desbloqueado para este cliente",
"disallowThisClient": "No permitir este cliente",
"allowThisClient": "Permitir este cliente",
"clientAllowedSuccessfully": "Cliente permitido correctamente",
"clientDisallowedSuccessfully": "Cliente no permitido correctamente",
"changesNotSaved": "Los cambios no han podido ser guardados",
"allowingClient": "Permitiendo cliente...",
"disallowingClient": "No permitiendo cliente..."
} }

View file

@ -75,7 +75,7 @@
"userNotEmpty": "Kullanıcı adı boş bırakılamaz", "userNotEmpty": "Kullanıcı adı boş bırakılamaz",
"passwordNotEmpty": "Şifre boş bırakılamaz", "passwordNotEmpty": "Şifre boş bırakılamaz",
"examplePath": "Örnek: /adguard", "examplePath": "Örnek: /adguard",
"helperPath": "Ters proxy kullanıyorsanız", "helperPath": "Eğer ters proxy kullanıyorsanız",
"aboutApp": "Uygulama hakkında", "aboutApp": "Uygulama hakkında",
"appVersion": "Uygulama sürümü", "appVersion": "Uygulama sürümü",
"createdBy": "Geliştirici", "createdBy": "Geliştirici",
@ -103,7 +103,7 @@
"logsCopiedClipboard": "Günlükler panoya kopyalandı", "logsCopiedClipboard": "Günlükler panoya kopyalandı",
"advancedSettings": "Gelişmiş ayarlar", "advancedSettings": "Gelişmiş ayarlar",
"dontCheckCertificate": "SSL sertifikasını kontrol etme", "dontCheckCertificate": "SSL sertifikasını kontrol etme",
"dontCheckCertificateDescription": "Sunucunun SSL sertifikası doğrulamasını geçersiz kılar", "dontCheckCertificateDescription": "Sunucunun SSL sertifikası doğrulamasını geçersiz kılar.",
"advancedSetupDescription": "Gelişmiş seçenekleri yönet", "advancedSetupDescription": "Gelişmiş seçenekleri yönet",
"settingsUpdatedSuccessfully": "Ayarlar başarıyla güncellendi.", "settingsUpdatedSuccessfully": "Ayarlar başarıyla güncellendi.",
"cannotUpdateSettings": "Ayarlar güncellenemiyor.", "cannotUpdateSettings": "Ayarlar güncellenemiyor.",
@ -131,11 +131,11 @@
"rewrite": "Yeniden Yaz", "rewrite": "Yeniden Yaz",
"status": "Durum", "status": "Durum",
"result": "Sonuç", "result": "Sonuç",
"time": "Zaman", "time": "Saat",
"blocklist": "Engelleme Listesi", "blocklist": "Engelleme Listesi",
"request": "İstek", "request": "İstek",
"domain": "Alan adı", "domain": "Alan adı",
"type": "Tip", "type": "Tür",
"clas": "Sınıf", "clas": "Sınıf",
"response": "Yanıt", "response": "Yanıt",
"dnsServer": "DNS sunucusu", "dnsServer": "DNS sunucusu",
@ -204,7 +204,7 @@
"noUpstreamServers": "Üst sunucu yok.", "noUpstreamServers": "Üst sunucu yok.",
"willBeUsedGeneralServers": "Genel üst sunucular kullanılacak.", "willBeUsedGeneralServers": "Genel üst sunucular kullanılacak.",
"added": "Eklenenler", "added": "Eklenenler",
"clientUpdatedSuccessfully": "İstemci başarıyla güncellendi", "clientUpdatedSuccessfully": "İstemci ayarları başarıyla güncellendi",
"clientNotUpdated": "İstemci güncellenemedi", "clientNotUpdated": "İstemci güncellenemedi",
"clientDeletedSuccessfully": "İstemci başarıyla kaldırıldı", "clientDeletedSuccessfully": "İstemci başarıyla kaldırıldı",
"clientNotDeleted": "İstemci silinemedi", "clientNotDeleted": "İstemci silinemedi",
@ -308,8 +308,8 @@
"addImportant": "Başına $important ekle", "addImportant": "Başına $important ekle",
"howCreateRules": "Özel kurallar nasıl oluşturulur?", "howCreateRules": "Özel kurallar nasıl oluşturulur?",
"examples": "Örnekler", "examples": "Örnekler",
"example1": "example.org (ornek.org) ve tüm alt alan adlarına erişimi engeller.", "example1": "example.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.", "example2": "example.org ve tüm alt alan adlarına erişimi engellemeyi kaldırır.",
"example3": "Yorum ekler.", "example3": "Yorum ekler.",
"example4": "Belirtilen düzenli ifadeye uyan alan adlarına erişimi engeller.", "example4": "Belirtilen düzenli ifadeye uyan alan adlarına erişimi engeller.",
"moreInformation": "Daha fazla bilgi", "moreInformation": "Daha fazla bilgi",
@ -418,7 +418,7 @@
"logSettingsNotLoaded": "Günlük ayarları yüklenemedi.", "logSettingsNotLoaded": "Günlük ayarları yüklenemedi.",
"updatingSettings": "Ayarlar güncelleniyor...", "updatingSettings": "Ayarlar güncelleniyor...",
"logsConfigUpdated": "Günlük ayarları başarıyla güncellendi", "logsConfigUpdated": "Günlük ayarları başarıyla güncellendi",
"logsConfigNotUpdated": "Günlük ayarları başarıyla güncellendi", "logsConfigNotUpdated": "Günlük ayarları güncellenemedi",
"deletingLogs": "Günlükler temizleniyor...", "deletingLogs": "Günlükler temizleniyor...",
"logsCleared": "Günlükler başarıyla temizlendi", "logsCleared": "Günlükler başarıyla temizlendi",
"logsNotCleared": "Günlükler temizlenemedi", "logsNotCleared": "Günlükler temizlenemedi",
@ -436,7 +436,7 @@
"parallelRequests": "Paralel istekler", "parallelRequests": "Paralel istekler",
"fastestIpAddress": "En hızlı IP adresi", "fastestIpAddress": "En hızlı IP adresi",
"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.", "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.", "parallelRequestsDescription": "Tüm üst sunucuları aynı anda sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanılı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.", "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.", "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.", "bootstrapDnsServersInfo": "Önyükleme DNS sunucuları, üst kaynaklarda belirttiğiniz DoH/DoT çözümleyicilerinin IP adreslerini çözmek için kullanılır.",
@ -469,7 +469,7 @@
"customIpDescription": "Manuel olarak ayarlanmış bir IP adresi ile yanıt verir.", "customIpDescription": "Manuel olarak ayarlanmış bir IP adresi ile yanıt verir.",
"dnsCacheConfig": "DNS önbellek yapılandırması", "dnsCacheConfig": "DNS önbellek yapılandırması",
"cacheSize": "Önbellek boyutu", "cacheSize": "Önbellek boyutu",
"inBytes": "Alınacak önbelleğin boyutunu ayarla (bayt olarak)", "inBytes": "Alınacak önbelleğin boyutunu ayarla (Bayt olarak)",
"overrideMinimumTtl": "Minimum kullanım süresini geçersiz kıl", "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 (TTL) saniye olarak ayarlayın.", "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", "overrideMaximumTtl": "Maksimum kullanım süresini geçersiz kıl",
@ -494,7 +494,7 @@
"dnsCacheConfigDescription": "Sunucunun DNS önbelleğini nasıl yöneteceğini yapılandır", "dnsCacheConfigDescription": "Sunucunun DNS önbelleğini nasıl yöneteceğini yapılandır",
"comment": "Yorum", "comment": "Yorum",
"address": "Adres", "address": "Adres",
"commentsDescription": "Yorumlar her zaman # işareti ile başlar. Onu eklemenize gerek yok, otomatik olarak eklenir.", "commentsDescription": "Yorumlar her zaman # işareti ile başlar. Eklemenize gerek yok, otomatik olarak eklenecektir.",
"encryptionSettings": "Şifreleme ayarları", "encryptionSettings": "Şifreleme ayarları",
"encryptionSettingsDescription": "Şifreleme (HTTPS/QUIC/TLS) desteği", "encryptionSettingsDescription": "Şifreleme (HTTPS/QUIC/TLS) desteği",
"loadingEncryptionSettings": "Şifreleme ayarları yükleniyor...", "loadingEncryptionSettings": "Şifreleme ayarları yükleniyor...",
@ -560,8 +560,8 @@
"validPrivateKey": "Geçerli özel anahtar", "validPrivateKey": "Geçerli özel anahtar",
"expirationDate": "Son kullanma tarihi", "expirationDate": "Son kullanma tarihi",
"keysNotMatch": "Geçersiz bir sertifika veya anahtar: tls: özel anahtar genel anahtarla eşleşmiyor.", "keysNotMatch": "Geçersiz bir sertifika veya anahtar: tls: özel anahtar genel anahtarla eşleşmiyor.",
"timeLogs": "Günlüklerde işlem süresi göster", "timeLogs": "Günlüklerde işlem süresini göster",
"timeLogsDescription": "Günlükler listesinde zaman yerine işlem süresini göster.", "timeLogsDescription": "Günlükler listesinde zaman yerine işlem süresini gösterir.",
"hostNames": "Ana bilgisayar adları", "hostNames": "Ana bilgisayar adları",
"keyType": "Anahtar türü", "keyType": "Anahtar türü",
"updateAvailable": "Güncelleme mevcut", "updateAvailable": "Güncelleme mevcut",
@ -626,7 +626,7 @@
"appUpdates": "Uygulama güncellemeleri", "appUpdates": "Uygulama güncellemeleri",
"usingLatestVersion": "En son sürümü kullanıyorsunuz", "usingLatestVersion": "En son sürümü kullanıyorsunuz",
"ipLogs": "Günlüklerde IP adresini göster", "ipLogs": "Günlüklerde IP adresini göster",
"ipLogsDescription": "Günlükler listesinde istemci adı yerine IP adresini göster.", "ipLogsDescription": "Günlükler listesinde istemci adı yerine IP adresini gösterir.",
"application": "Uygulama", "application": "Uygulama",
"combinedChart": "Birleştirilmiş grafik", "combinedChart": "Birleştirilmiş grafik",
"combinedChartDescription": "Tüm grafikleri bir araya getirir.", "combinedChartDescription": "Tüm grafikleri bir araya getirir.",
@ -688,7 +688,7 @@
"yourVersion": "Yüklü sürüm: {version}", "yourVersion": "Yüklü sürüm: {version}",
"minimumRequiredVersion": "Gerekli minimum sürüm: {version}", "minimumRequiredVersion": "Gerekli minimum sürüm: {version}",
"topUpstreams": "Öne çıkan DNS sunucuları", "topUpstreams": "Öne çıkan DNS sunucuları",
"averageUpstreamResponseTime": "DNS sunucusu ortalama işlem süresi" , "averageUpstreamResponseTime": "DNS sunucuları işlem süresi" ,
"dhcpNotAvailable": "DHCP sunucusu kullanılamıyor.", "dhcpNotAvailable": "DHCP sunucusu kullanılamıyor.",
"osServerInstalledIncompatible": "AdGuard Home, işletim sisteminizde DHCP sunucusu çalıştıramıyor.", "osServerInstalledIncompatible": "AdGuard Home, işletim sisteminizde DHCP sunucusu çalıştıramıyor.",
"resetSettings": "Ayarları sıfırla", "resetSettings": "Ayarları sıfırla",
@ -721,7 +721,7 @@
"unblockingClient": "İstemci engeli kaldırılıyor...", "unblockingClient": "İstemci engeli kaldırılıyor...",
"upstreamDnsCacheConfiguration": "DNS önbellek yapılandırması", "upstreamDnsCacheConfiguration": "DNS önbellek yapılandırması",
"enableDnsCachingClient": "Bu istemci için DNS önbelleğe almayı etkinleştir", "enableDnsCachingClient": "Bu istemci için DNS önbelleğe almayı etkinleştir",
"dnsCacheSize": "DNS önbellek boyutu (bayt cinsinden)", "dnsCacheSize": "DNS önbellek boyutu (Bayt cinsinden)",
"nameInvalid": "Ad gereklidir", "nameInvalid": "Ad gereklidir",
"oneIdentifierRequired": "En az bir tanımlayıcı gereklidir", "oneIdentifierRequired": "En az bir tanımlayıcı gereklidir",
"dnsCacheNumber": "DNS önbellek boyutu bir rakam içermelidir", "dnsCacheNumber": "DNS önbellek boyutu bir rakam içermelidir",
@ -754,7 +754,9 @@
"statisticsSettingsDescription": "İstatistikler için veri toplamayı yapılandır", "statisticsSettingsDescription": "İstatistikler için veri toplamayı yapılandır",
"loadingStatisticsSettings": "İstatistik ayarları yükleniyor...", "loadingStatisticsSettings": "İstatistik ayarları yükleniyor...",
"statisticsSettingsLoadError": "İstatistik ayarları yüklenirken bir hata oluştu.", "statisticsSettingsLoadError": "İstatistik ayarları yüklenirken bir hata oluştu.",
"customTimeInHours": "Özel zaman (saat olarak)", "statisticsConfigUpdated": "İstatistik ayarları başarıyla güncellendi",
"statisticsConfigNotUpdated": "İstatistik ayarları güncellenemedi",
"customTimeInHours": "Özel zaman (Saat olarak)",
"invalidTime": "Geçersiz zaman", "invalidTime": "Geçersiz zaman",
"removeDomain": "Alan adını kaldır", "removeDomain": "Alan adını kaldır",
"addDomain": "Alan adı ekle", "addDomain": "Alan adı ekle",
@ -774,5 +776,8 @@
"showHide": "Göster/gizle", "showHide": "Göster/gizle",
"noElementsReorderMessage": "Burada yeniden sıralamak için göster/gizle sekmesindeki bazı öğeleri etkinleştirin.", "noElementsReorderMessage": "Burada yeniden sıralamak için göster/gizle sekmesindeki bazı öğeleri etkinleştirin.",
"enablePlainDns": "Düz DNS'i etkinleştir", "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." "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.",
"date": "Tarih",
"loadingChangelog": "Değişiklikler yükleniyor...",
"invalidIpOrUrl": "Geçersiz IP adresi veya URL"
} }

View file

@ -10,6 +10,16 @@ import 'package:adguard_home_manager/models/safe_search.dart';
import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart';
import 'package:adguard_home_manager/models/clients.dart'; import 'package:adguard_home_manager/models/clients.dart';
class ClientInitialData {
final String name;
final String ip;
const ClientInitialData({
required this.name,
required this.ip,
});
}
class ControllerListItem { class ControllerListItem {
final String id; final String id;
final TextEditingController controller; final TextEditingController controller;
@ -25,13 +35,15 @@ class ClientScreen extends StatefulWidget {
final void Function(Client) onConfirm; final void Function(Client) onConfirm;
final void Function(Client)? onDelete; final void Function(Client)? onDelete;
final bool fullScreen; final bool fullScreen;
final ClientInitialData? initialData;
const ClientScreen({ const ClientScreen({
super.key, super.key,
this.client, this.client,
required this.onConfirm, required this.onConfirm,
this.onDelete, this.onDelete,
required this.fullScreen required this.fullScreen,
this.initialData,
}); });
@override @override
@ -146,6 +158,13 @@ class _ClientScreenState extends State<ClientScreen> {
_blockedServicesSchedule = widget.client!.blockedServicesSchedule!; _blockedServicesSchedule = widget.client!.blockedServicesSchedule!;
} }
} }
if (widget.initialData != null) {
nameController.text = widget.initialData!.name;
identifiersControllers[0] = ControllerListItem(
id: uuid.v4(),
controller: TextEditingController(text: widget.initialData!.ip)
);
}
super.initState(); super.initState();
} }
@ -216,71 +235,90 @@ class _ClientScreenState extends State<ClientScreen> {
if (widget.fullScreen == true) { if (widget.fullScreen == true) {
return Dialog.fullscreen( return Material(
child: Scaffold( child: NestedScrollView(
appBar: AppBar( headerSliverBuilder: (context, innerBoxIsScrolled) => [
leading: IconButton( SliverOverlapAbsorber(
onPressed: () => Navigator.pop(context), handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
icon: const Icon(Icons.close) sliver: SliverAppBar.large(
), pinned: true,
title: Text( floating: true,
widget.client != null centerTitle: false,
? AppLocalizations.of(context)!.client forceElevated: innerBoxIsScrolled,
: AppLocalizations.of(context)!.addClient leading: IconButton(
), onPressed: () => Navigator.pop(context),
actions: actions(), icon: const Icon(Icons.close)
), ),
title: Text(
widget.client != null
? AppLocalizations.of(context)!.client
: AppLocalizations.of(context)!.addClient
),
actions: actions(),
)
)
],
body: SafeArea( body: SafeArea(
child: ListView( top: false,
controller: _scrollController, bottom: false,
children: [ child: Builder(
if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors( builder: (context) => CustomScrollView(
nameValid: _nameValid, slivers: [
identifiersValid: _identifiersValid, SliverOverlapInjector(
dnsCacheValid: _dnsCacheValid handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
), ),
ClientForm( SliverList.list(
isFullScreen: true, children: [
client: widget.client, if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors(
nameController: nameController, nameValid: _nameValid,
identifiersControllers: identifiersControllers, identifiersValid: _identifiersValid,
selectedTags: selectedTags, dnsCacheValid: _dnsCacheValid
useGlobalSettingsFiltering: useGlobalSettingsFiltering, ),
enableFiltering: enableFiltering, ClientForm(
enableParentalControl: enableParentalControl, isFullScreen: true,
enableSafeBrowsing: enableSafeBrowsing, client: widget.client,
enableSafeSearch: enableSafeSearch, nameController: nameController,
safeSearch: safeSearch, identifiersControllers: identifiersControllers,
blockedServices: blockedServices, selectedTags: selectedTags,
updateBlockedServices: (v) => setState(() => blockedServices = v), useGlobalSettingsFiltering: useGlobalSettingsFiltering,
upstreamServers: upstreamServers, enableFiltering: enableFiltering,
updateUpstreamServers: (v) => setState(() => upstreamServers = v), enableParentalControl: enableParentalControl,
defaultSafeSearch: defaultSafeSearch, enableSafeBrowsing: enableSafeBrowsing,
useGlobalSettingsServices: useGlobalSettingsServices, enableSafeSearch: enableSafeSearch,
updateSelectedTags: (v) => setState(() => selectedTags = v), safeSearch: safeSearch,
updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v), blockedServices: blockedServices,
enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering, updateBlockedServices: (v) => setState(() => blockedServices = v),
updateEnableFiltering: (v) => setState(() => enableFiltering = v), upstreamServers: upstreamServers,
updateEnableParentalControl: (v) => setState(() => enableParentalControl = v), updateUpstreamServers: (v) => setState(() => upstreamServers = v),
updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v), defaultSafeSearch: defaultSafeSearch,
updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v), useGlobalSettingsServices: useGlobalSettingsServices,
updateSafeSearch: (v) => setState(() => safeSearch = v), updateSelectedTags: (v) => setState(() => selectedTags = v),
updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v), updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v),
ignoreClientQueryLog: _ignoreClientQueryLog, enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering,
ignoreClientStatistics: _ignoreClientStatistics, updateEnableFiltering: (v) => setState(() => enableFiltering = v),
updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v), updateEnableParentalControl: (v) => setState(() => enableParentalControl = v),
updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v), updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v),
enableDnsCache: _enableDnsCache, updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v),
updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v), updateSafeSearch: (v) => setState(() => safeSearch = v),
dnsCacheField: _dnsCacheField, updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v),
dnsCacheError: _dnsCacheError, ignoreClientQueryLog: _ignoreClientQueryLog,
updateDnsCacheError: (v) => setState(() => _dnsCacheError = v), ignoreClientStatistics: _ignoreClientStatistics,
blockedServicesSchedule: _blockedServicesSchedule, updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v),
setBlockedServicesSchedule: (v) => setState(() => _blockedServicesSchedule = v), updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v),
), enableDnsCache: _enableDnsCache,
], updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v),
), dnsCacheField: _dnsCacheField,
), dnsCacheError: _dnsCacheError,
updateDnsCacheError: (v) => setState(() => _dnsCacheError = v),
blockedServicesSchedule: _blockedServicesSchedule,
setBlockedServicesSchedule: (v) => setState(() => _blockedServicesSchedule = v),
),
],
)
],
),
)
)
), ),
); );
} }

View file

@ -80,6 +80,7 @@ void openClientFormModal({
Client? client, Client? client,
required void Function(Client) onConfirm, required void Function(Client) onConfirm,
void Function(Client)? onDelete, void Function(Client)? onDelete,
ClientInitialData? initialData,
}) { }) {
showGeneralDialog( showGeneralDialog(
context: context, context: context,
@ -105,6 +106,7 @@ void openClientFormModal({
client: client, client: client,
onConfirm: onConfirm, onConfirm: onConfirm,
onDelete: onDelete, onDelete: onDelete,
initialData: initialData,
), ),
); );
} }

View file

@ -131,146 +131,175 @@ class _LogsListClientState extends State<LogsListClient> {
setState(() => previousIp = widget.ip); setState(() => previousIp = widget.ip);
} }
return Scaffold( return Material(
appBar: AppBar( child: NestedScrollView(
title: Text(widget.name != null && widget.name != '' ? widget.name! : widget.ip), headerSliverBuilder: (context, innerBoxIsScrolled) => [
centerTitle: true, SliverOverlapAbsorber(
surfaceTintColor: isDesktop(MediaQuery.of(context).size.width) handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
? Colors.transparent sliver: SliverAppBar.large(
: null, pinned: true,
actions: [ floating: true,
if (!(Platform.isAndroid || Platform.isIOS)) ...[ centerTitle: false,
IconButton( forceElevated: innerBoxIsScrolled,
onPressed: fetchLogs, title: SafeArea(
icon: const Icon(Icons.refresh_rounded), child: Column(
tooltip: AppLocalizations.of(context)!.refresh, crossAxisAlignment: CrossAxisAlignment.start,
), children: [
const SizedBox(width: 8) Text(
] AppLocalizations.of(context)!.client,
], style: const TextStyle(
), fontSize: 24
body: SafeArea( ),
child: Builder( ),
builder: (context) { const SizedBox(height: 4),
switch (loadStatus) { Text(
case 0: widget.name != null && widget.name != '' ? widget.name! : widget.ip,
return SizedBox( style: TextStyle(
width: double.maxFinite, fontSize: 14,
child: Column( fontWeight: FontWeight.w500,
mainAxisAlignment: MainAxisAlignment.center, color: Theme.of(context).colorScheme.secondary
crossAxisAlignment: CrossAxisAlignment.center, ),
children: [ )
const CircularProgressIndicator(), ],
const SizedBox(height: 30), ),
Text( ),
AppLocalizations.of(context)!.loadingLogs, surfaceTintColor: isDesktop(MediaQuery.of(context).size.width)
textAlign: TextAlign.center, ? Colors.transparent
style: TextStyle( : null,
fontSize: 22, actions: [
color: Theme.of(context).colorScheme.onSurfaceVariant, if (!(Platform.isAndroid || Platform.isIOS)) ...[
), IconButton(
) onPressed: fetchLogs,
], icon: const Icon(Icons.refresh_rounded),
tooltip: AppLocalizations.of(context)!.refresh,
), ),
); const SizedBox(width: 8)
]
case 1: ],
if (logsData!.data.isNotEmpty) { )
return RefreshIndicator( )
onRefresh: fetchLogs, ],
child: ListView.builder( body: SafeArea(
controller: scrollController, top: false,
padding: const EdgeInsets.only(top: 0), bottom: false,
itemCount: isLoadingMore == true child: Builder(
? logsData!.data.length+1 builder: (context) => RefreshIndicator(
: logsData!.data.length, onRefresh: fetchLogs,
itemBuilder: (context, index) { displacement: 95,
if (isLoadingMore == true && index == logsData!.data.length) { child: CustomScrollView(
return const Padding( slivers: [
padding: EdgeInsets.symmetric(vertical: 20), SliverOverlapInjector(
child: Center( handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
child: CircularProgressIndicator(), ),
if (loadStatus == 0) SliverFillRemaining(
child: SizedBox(
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.loadingLogs,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
); )
} ],
else { ),
return LogTile( )
log: logsData!.data[index], ),
index: index, if (loadStatus == 1 && logsData!.data.isNotEmpty) SliverList.builder(
length: logsData!.data.length, itemCount: isLoadingMore == true
useAlwaysNormalTile: true, ? logsData!.data.length+1
onLogTap: (log) => { : logsData!.data.length,
if (width > 700) { itemBuilder: (context, index) {
showDialog( if (isLoadingMore == true && index == logsData!.data.length) {
context: context, return const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Center(
child: CircularProgressIndicator(),
),
);
}
else {
return LogTile(
log: logsData!.data[index],
index: index,
length: logsData!.data.length,
useAlwaysNormalTile: true,
onLogTap: (log) => {
if (width > 700) {
showDialog(
context: context,
builder: (context) => LogDetailsScreen(
log: log,
dialog: true
)
)
}
else {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LogDetailsScreen( builder: (context) => LogDetailsScreen(
log: log, log: log,
dialog: true dialog: false
) )
) )
} )
else { }
Navigator.of(context).push( },
MaterialPageRoute( twoColumns: widget.splitView,
builder: (context) => LogDetailsScreen( );
log: log,
dialog: false
)
)
)
}
},
twoColumns: widget.splitView,
);
}
} }
), }
); ),
} if (loadStatus == 1 && logsData!.data.isEmpty) SliverFillRemaining(
else { child: Center(
return Center( child: Text(
child: Text( AppLocalizations.of(context)!.noLogsDisplay,
AppLocalizations.of(context)!.noLogsDisplay,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
);
}
case 2:
return SizedBox(
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.logsNotLoaded,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 22, fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
) ),
], ),
), ),
); if (loadStatus == 2) SliverFillRemaining(
child: SizedBox(
default: width: double.maxFinite,
return const SizedBox(); child: Column(
} mainAxisAlignment: MainAxisAlignment.center,
}, crossAxisAlignment: CrossAxisAlignment.center,
), children: [
) const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.logsNotLoaded,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
),
)
],
),
),
)
)
),
); );
} }
} }

View file

@ -14,7 +14,7 @@ import 'package:adguard_home_manager/classes/process_modal.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart';
class ClientsFab extends StatelessWidget { class ClientsFab extends StatelessWidget {
const ClientsFab({Key? key}) : super(key: key); const ClientsFab({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View file

@ -85,38 +85,130 @@ class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreen> {
if (widget.fullScreen == true) { if (widget.fullScreen == true) {
return Dialog.fullscreen( return Dialog.fullscreen(
child: Scaffold( child: Material(
appBar: AppBar( child: NestedScrollView(
leading: CloseButton(onPressed: () => Navigator.pop(context)), headerSliverBuilder: (context, innerBoxIsScrolled) => [
title: Text(AppLocalizations.of(context)!.blockedServices), SliverOverlapAbsorber(
actions: [ handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
IconButton( sliver: SliverAppBar.large(
onPressed: updateBlockedServices, pinned: true,
icon: const Icon( floating: true,
Icons.save_rounded centerTitle: false,
), forceElevated: innerBoxIsScrolled,
tooltip: AppLocalizations.of(context)!.save, leading: CloseButton(onPressed: () => Navigator.pop(context)),
), title: Text(AppLocalizations.of(context)!.blockedServices),
const SizedBox(width: 10) actions: [
], IconButton(
), onPressed: updateBlockedServices,
body: SafeArea( icon: const Icon(
child: RefreshIndicator( Icons.save_rounded
onRefresh: () async { ),
final result = await filteringProvider.loadBlockedServices(); tooltip: AppLocalizations.of(context)!.save,
if (result == false) { ),
showSnacbkar( const SizedBox(width: 10)
appConfigProvider: appConfigProvider, ],
label: AppLocalizations.of(context)!.blockedServicesListNotLoaded, )
color: Colors.red
);
}
},
child: _Content(
values: values,
updateValues: updateValues,
) )
), ],
body: SafeArea(
top: false,
bottom: true,
child: Builder(
builder: (context) => CustomScrollView(
slivers: [
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
if (filteringProvider.blockedServicesLoadStatus == LoadStatus.loading) Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.loadingBlockedServicesList,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
),
if (filteringProvider.blockedServicesLoadStatus == LoadStatus.loaded) SliverList.builder(
itemCount: filteringProvider.blockedServices!.services.length,
itemBuilder: (context, index) => Material(
color: Colors.transparent,
child: InkWell(
onTap: () => updateValues(
values.contains(filteringProvider.blockedServices!.services[index].id),
filteringProvider.blockedServices!.services[index]
),
child: Padding(
padding: const EdgeInsets.only(
top: 6,
bottom: 6,
right: 12,
left: 24
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
filteringProvider.blockedServices!.services[index].name,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface
),
),
Checkbox(
value: values.contains(filteringProvider.blockedServices!.services[index].id),
onChanged: (value) => updateValues(
value!,
filteringProvider.blockedServices!.services[index]
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)
),
)
],
),
),
),
)
),
if (filteringProvider.blockedServicesLoadStatus == LoadStatus.error) Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.blockedServicesListNotLoaded,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
)
],
),
)
)
), ),
), ),
); );
@ -162,9 +254,105 @@ class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreen> {
), ),
), ),
Expanded( Expanded(
child: _Content( child: Builder(
values: values, builder: (ctx) {
updateValues: updateValues, switch (filteringProvider.blockedServicesLoadStatus) {
case LoadStatus.loading:
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.loadingBlockedServicesList,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
case LoadStatus.loaded:
return ListView.builder(
itemCount: filteringProvider.blockedServices!.services.length,
itemBuilder: (context, index) => Material(
color: Colors.transparent,
child: InkWell(
onTap: () => updateValues(
values.contains(filteringProvider.blockedServices!.services[index].id),
filteringProvider.blockedServices!.services[index]
),
child: Padding(
padding: const EdgeInsets.only(
top: 6,
bottom: 6,
right: 12,
left: 24
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
filteringProvider.blockedServices!.services[index].name,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface
),
),
Checkbox(
value: values.contains(filteringProvider.blockedServices!.services[index].id),
onChanged: (value) => updateValues(
value!,
filteringProvider.blockedServices!.services[index]
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)
),
)
],
),
),
),
)
);
case LoadStatus.error:
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.blockedServicesListNotLoaded,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
default:
return const SizedBox();
}
},
) )
), ),
], ],
@ -175,117 +363,6 @@ class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreen> {
} }
} }
class _Content extends StatelessWidget {
final List<String> values;
final void Function(bool value, BlockedService item) updateValues;
const _Content({
required this.values,
required this.updateValues,
});
@override
Widget build(BuildContext context) {
final filteringProvider = Provider.of<FilteringProvider>(context);
switch (filteringProvider.blockedServicesLoadStatus) {
case LoadStatus.loading:
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.loadingBlockedServicesList,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
case LoadStatus.loaded:
return ListView.builder(
itemCount: filteringProvider.blockedServices!.services.length,
itemBuilder: (context, index) => Material(
color: Colors.transparent,
child: InkWell(
onTap: () => updateValues(
values.contains(filteringProvider.blockedServices!.services[index].id),
filteringProvider.blockedServices!.services[index]
),
child: Padding(
padding: const EdgeInsets.only(
top: 6,
bottom: 6,
right: 12,
left: 24
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
filteringProvider.blockedServices!.services[index].name,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface
),
),
Checkbox(
value: values.contains(filteringProvider.blockedServices!.services[index].id),
onChanged: (value) => updateValues(
value!,
filteringProvider.blockedServices!.services[index]
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)
),
)
],
),
),
),
)
);
case LoadStatus.error:
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.blockedServicesListNotLoaded,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
default:
return const SizedBox();
}
}
}
void openBlockedServicesModal({ void openBlockedServicesModal({
required BuildContext context, required BuildContext context,

View file

@ -55,44 +55,66 @@ class _AddCustomRuleState extends State<AddCustomRule> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (widget.fullScreen == true) { if (widget.fullScreen == true) {
return Dialog.fullscreen( return Dialog.fullscreen(
child: Scaffold( child: Material(
appBar: AppBar( child: NestedScrollView(
leading: CloseButton(onPressed: () => Navigator.pop(context)), headerSliverBuilder: (context, innerBoxIsScrolled) => [
title: Text(AppLocalizations.of(context)!.addCustomRule), SliverOverlapAbsorber(
actions: [ handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
IconButton( sliver: SliverAppBar.large(
onPressed: _checkValidValues() == true pinned: true,
? () { floating: true,
Navigator.pop(context); centerTitle: false,
widget.onConfirm( forceElevated: innerBoxIsScrolled,
_buildRule( leading: CloseButton(onPressed: () => Navigator.pop(context)),
domainController: _domainController, title: Text(AppLocalizations.of(context)!.addCustomRule),
important: _addImportant, actions: [
preset: _preset IconButton(
) onPressed: _checkValidValues() == true
); ? () {
} Navigator.pop(context);
: null, widget.onConfirm(
icon: const Icon(Icons.check) _buildRule(
), domainController: _domainController,
const SizedBox(width: 10) important: _addImportant,
], preset: _preset
), )
body: SafeArea( );
child: ListView( }
children: [ : null,
_CustomRuleEditor( icon: const Icon(Icons.check)
domainController: _domainController, ),
domainError: _domainError, const SizedBox(width: 10)
important: _addImportant, ],
preset: _preset, )
setImportant: (v) => setState(() => _addImportant = v),
setPreset: (v) => setState(() => _preset = v),
validateDomain: validateDomain
) )
] ],
body: SafeArea(
top: false,
bottom: true,
child: Builder(
builder: (context) => CustomScrollView(
slivers: [
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
SliverList.list(
children: [
_CustomRuleEditor(
domainController: _domainController,
domainError: _domainError,
important: _addImportant,
preset: _preset,
setImportant: (v) => setState(() => _addImportant = v),
setPreset: (v) => setState(() => _preset = v),
validateDomain: validateDomain
)
]
)
],
),
)
)
), ),
)
), ),
); );
} }

View file

@ -37,29 +37,51 @@ class _EditCustomRulesState extends State<EditCustomRules> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (widget.fullScreen == true) { if (widget.fullScreen == true) {
return Dialog.fullscreen( return Dialog.fullscreen(
child: Scaffold( child: Material(
appBar: AppBar( child: NestedScrollView(
leading: CloseButton(onPressed: () => Navigator.pop(context)), headerSliverBuilder: (context, innerBoxIsScrolled) => [
title: Text(AppLocalizations.of(context)!.editCustomRules), SliverOverlapAbsorber(
actions: [ handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
IconButton( sliver: SliverAppBar.large(
onPressed: () { pinned: true,
Navigator.pop(context); floating: true,
widget.onConfirm(_fieldController.text.split("\n")); centerTitle: false,
}, forceElevated: innerBoxIsScrolled,
icon: const Icon(Icons.save_rounded), leading: CloseButton(onPressed: () => Navigator.pop(context)),
tooltip: AppLocalizations.of(context)!.save, title: Text(AppLocalizations.of(context)!.editCustomRules),
), actions: [
const SizedBox(width: 10) 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(
top: false,
bottom: true,
child: Builder(
builder: (context) => CustomScrollView(
slivers: [
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
SliverList.list(
children: [
_CustomRulesRawEditor(fieldController: _fieldController)
]
)
],
),
)
)
), ),
body: SafeArea(
child: ListView(
children: [
_CustomRulesRawEditor(fieldController: _fieldController)
]
),
)
), ),
); );
} }

View file

@ -4,12 +4,17 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
import 'package:adguard_home_manager/screens/clients/client/client_screen.dart';
import 'package:adguard_home_manager/widgets/options_menu.dart'; import 'package:adguard_home_manager/widgets/options_menu.dart';
import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/providers/status_provider.dart';
import 'package:adguard_home_manager/providers/filtering_provider.dart';
import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/classes/process_modal.dart';
import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/functions/snackbar.dart';
import 'package:adguard_home_manager/functions/copy_clipboard.dart'; import 'package:adguard_home_manager/functions/copy_clipboard.dart';
import 'package:adguard_home_manager/models/clients.dart';
import 'package:adguard_home_manager/providers/clients_provider.dart';
import 'package:adguard_home_manager/models/menu_option.dart'; import 'package:adguard_home_manager/models/menu_option.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart';
import 'package:adguard_home_manager/functions/get_filtered_status.dart'; import 'package:adguard_home_manager/functions/get_filtered_status.dart';
@ -40,6 +45,8 @@ class LogTile extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final appConfigProvider = Provider.of<AppConfigProvider>(context); final appConfigProvider = Provider.of<AppConfigProvider>(context);
final statusProvider = Provider.of<StatusProvider>(context); final statusProvider = Provider.of<StatusProvider>(context);
final clientsProvider = Provider.of<ClientsProvider>(context);
final filteringProvider = Provider.of<FilteringProvider>(context);
Widget logStatusWidget({ Widget logStatusWidget({
required IconData icon, required IconData icon,
@ -118,6 +125,114 @@ class LogTile extends StatelessWidget {
} }
} }
void confirmAddClient(Client client) async {
ProcessModal processModal = ProcessModal();
processModal.open(AppLocalizations.of(context)!.addingClient);
final result = await clientsProvider.addClient(client);
processModal.close();
if (result == true) {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientAddedSuccessfully,
color: Colors.green
);
}
else {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientNotAdded,
color: Colors.red
);
}
}
void blockUnblockRuleClient() async {
ProcessModal processModal = ProcessModal();
processModal.open(AppLocalizations.of(context)!.addingRule);
final rule = isDomainBlocked(log.reason) == true
? "@@||${log.question.name}^\$client='${log.client}'"
: "||${log.question.name}^\$client='${log.client}'";
final result = await filteringProvider.addCustomRule(rule);
processModal.close();
if (!context.mounted) return;
if (result == true) {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: isDomainBlocked(log.reason) == true
? AppLocalizations.of(context)!.domainUnblockedThisClient(log.question.name!)
: AppLocalizations.of(context)!.domainBlockedThisClient(log.question.name!),
color: Colors.green
);
}
else {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.ruleNotAdded,
color: Colors.red
);
}
}
void allowDisallowClient() async {
ProcessModal processModal = ProcessModal();
processModal.open(
log.clientInfo!.disallowed == true
? AppLocalizations.of(context)!.allowingClient
: AppLocalizations.of(context)!.disallowingClient
);
final result = await clientsProvider.addClientList(
log.client,
log.clientInfo!.disallowed == true
? AccessSettingsList.allowed
: AccessSettingsList.disallowed
);
processModal.close();
if (!context.mounted) return;
if (result.successful == true) {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientAddedSuccessfully,
color: Colors.green
);
}
else if (result.successful == false && result.content == 'client_another_list') {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientAnotherList,
color: Colors.red
);
}
else {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.changesNotSaved,
color: Colors.red
);
}
}
void openAddClient() {
Future.delayed(
const Duration(milliseconds: 0),
() => openClientFormModal(
context: context,
width: MediaQuery.of(context).size.width,
onConfirm: confirmAddClient,
initialData: ClientInitialData(name: "Client ${log.client}", ip: log.client)
)
);
}
final domainBlocked = isDomainBlocked(log.reason); final domainBlocked = isDomainBlocked(log.reason);
if (twoColumns && !(useAlwaysNormalTile == true)) { if (twoColumns && !(useAlwaysNormalTile == true)) {
@ -141,6 +256,29 @@ class LogTile extends StatelessWidget {
newStatus: domainBlocked == true ? 'unblock' : 'block' newStatus: domainBlocked == true ? 'unblock' : 'block'
) )
), ),
if (filteringProvider.filtering != null) MenuOption(
title: domainBlocked == true
? AppLocalizations.of(context)!.unblockThisClientOnly
: AppLocalizations.of(context)!.blockThisClientOnly,
icon: domainBlocked == true
? Icons.check_rounded
: Icons.block_rounded,
action: blockUnblockRuleClient
),
if (log.clientInfo?.name == "") MenuOption(
title: AppLocalizations.of(context)!.addPersistentClient,
icon: Icons.add_rounded,
action: openAddClient
),
MenuOption(
title: log.clientInfo!.disallowed == true
? AppLocalizations.of(context)!.allowThisClient
: AppLocalizations.of(context)!.disallowThisClient,
icon: log.clientInfo!.disallowed == true
? Icons.check_rounded
: Icons.block_rounded,
action: allowDisallowClient
),
if (log.question.name != null) MenuOption( if (log.question.name != null) MenuOption(
title: AppLocalizations.of(context)!.copyClipboard, title: AppLocalizations.of(context)!.copyClipboard,
icon: Icons.copy_rounded, icon: Icons.copy_rounded,
@ -319,6 +457,29 @@ class LogTile extends StatelessWidget {
newStatus: domainBlocked == true ? 'unblock' : 'block' newStatus: domainBlocked == true ? 'unblock' : 'block'
) )
), ),
if (filteringProvider.filtering != null) MenuOption(
title: domainBlocked == true
? AppLocalizations.of(context)!.unblockThisClientOnly
: AppLocalizations.of(context)!.blockThisClientOnly,
icon: domainBlocked == true
? Icons.check_rounded
: Icons.block_rounded,
action: blockUnblockRuleClient
),
if (log.clientInfo?.name == "") MenuOption(
title: AppLocalizations.of(context)!.addPersistentClient,
icon: Icons.add_rounded,
action: openAddClient
),
MenuOption(
title: log.clientInfo!.disallowed == true
? AppLocalizations.of(context)!.allowThisClient
: AppLocalizations.of(context)!.disallowThisClient,
icon: log.clientInfo!.disallowed == true
? Icons.check_rounded
: Icons.block_rounded,
action: allowDisallowClient
),
if (log.question.name != null) MenuOption( if (log.question.name != null) MenuOption(
title: AppLocalizations.of(context)!.copyClipboard, title: AppLocalizations.of(context)!.copyClipboard,
icon: Icons.copy_rounded, icon: Icons.copy_rounded,

View file

@ -1,14 +1,16 @@
// ignore_for_file: use_build_context_synchronously // ignore_for_file: use_build_context_synchronously
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:adguard_home_manager/screens/logs/logs_list.dart'; import 'package:adguard_home_manager/screens/logs/logs_list.dart';
import 'package:adguard_home_manager/screens/logs/details/log_details_screen.dart'; import 'package:adguard_home_manager/screens/logs/details/log_details_screen.dart';
import 'package:adguard_home_manager/models/logs.dart'; import 'package:adguard_home_manager/models/logs.dart';
import 'package:adguard_home_manager/providers/filtering_provider.dart';
class Logs extends StatefulWidget { class Logs extends StatefulWidget {
const Logs({Key? key}) : super(key: key); const Logs({super.key});
@override @override
State<Logs> createState() => _LogsState(); State<Logs> createState() => _LogsState();
@ -17,6 +19,12 @@ class Logs extends StatefulWidget {
class _LogsState extends State<Logs> { class _LogsState extends State<Logs> {
Log? _selectedLog; Log? _selectedLog;
@override
void initState() {
Provider.of<FilteringProvider>(context, listen: false).fetchFilters();
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return LayoutBuilder( return LayoutBuilder(

View file

@ -8,11 +8,14 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/screens/logs/filters/logs_filters_modal.dart'; import 'package:adguard_home_manager/screens/logs/filters/logs_filters_modal.dart';
import 'package:adguard_home_manager/config/globals.dart';
import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/constants/enums.dart';
import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/desktop_mode.dart';
import 'package:adguard_home_manager/models/applied_filters.dart'; import 'package:adguard_home_manager/models/applied_filters.dart';
import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart';
final GlobalKey _searchButtonKey = GlobalKey();
class LogsListAppBar extends StatelessWidget { class LogsListAppBar extends StatelessWidget {
final bool innerBoxIsScrolled; final bool innerBoxIsScrolled;
final bool showDivider; final bool showDivider;
@ -52,6 +55,25 @@ class LogsListAppBar extends StatelessWidget {
} }
} }
void showSearchDialog() {
showDialog(
context: context,
builder: (context) => _Search(
searchButtonRenderBox: _searchButtonKey.currentContext?.findRenderObject() as RenderBox?,
onSearch: (v) {
logsProvider.setAppliedFilters(
AppliedFiters(
selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus,
searchText: v != "" ? v : null,
clients: logsProvider.appliedFilters.clients
)
);
logsProvider.filterLogs();
},
),
);
}
final Map<String, String> translatedString = { final Map<String, String> translatedString = {
"all": AppLocalizations.of(context)!.all, "all": AppLocalizations.of(context)!.all,
"filtered": AppLocalizations.of(context)!.filtered, "filtered": AppLocalizations.of(context)!.filtered,
@ -77,22 +99,8 @@ class LogsListAppBar extends StatelessWidget {
tooltip: AppLocalizations.of(context)!.refresh, tooltip: AppLocalizations.of(context)!.refresh,
), ),
if (logsProvider.loadStatus == LoadStatus.loaded) IconButton( if (logsProvider.loadStatus == LoadStatus.loaded) IconButton(
onPressed: () => showDialog( key: _searchButtonKey,
context: context, onPressed: showSearchDialog,
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), icon: const Icon(Icons.search_rounded),
tooltip: AppLocalizations.of(context)!.search, tooltip: AppLocalizations.of(context)!.search,
), ),
@ -235,11 +243,11 @@ class LogsListAppBar extends StatelessWidget {
class _Search extends StatefulWidget { class _Search extends StatefulWidget {
final void Function(String) onSearch; final void Function(String) onSearch;
final bool hasTopBar; final RenderBox? searchButtonRenderBox;
const _Search({ const _Search({
required this.onSearch, required this.onSearch,
required this.hasTopBar, required this.searchButtonRenderBox,
}); });
@override @override
@ -261,67 +269,75 @@ class _SearchState extends State<_Search> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final logsProvider = Provider.of<LogsProvider>(context); final logsProvider = Provider.of<LogsProvider>(context);
final position = widget.searchButtonRenderBox?.localToGlobal(Offset.zero);
final topPadding = MediaQuery.of(globalNavigatorKey.currentContext!).viewPadding.top;
return GestureDetector( return GestureDetector(
onTap: () => Navigator.pop(context), onTap: () => Navigator.pop(context),
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: Column( child: LayoutBuilder(
mainAxisSize: MainAxisSize.min, builder: (context, constraints) {
children: [ final double width = constraints.maxWidth - 32 > 500 ? 500 : constraints.maxWidth - 32;
GestureDetector( return Stack(
onTap: () => {}, alignment: Alignment.topCenter,
child: ConstrainedBox( children: [
constraints: const BoxConstraints(maxWidth: 500), Positioned(
child: Container( top: position != null ? position.dy - topPadding : topPadding,
margin: widget.hasTopBar child: SizedBox(
? const EdgeInsets.symmetric(horizontal: 16) width: width,
: const EdgeInsets.all(16), child: GestureDetector(
decoration: BoxDecoration( onTap: () => {},
color: Theme.of(context).colorScheme.surface, child: Container(
borderRadius: BorderRadius.circular(16) decoration: BoxDecoration(
), color: Theme.of(context).colorScheme.surface,
child: ClipRRect( borderRadius: BorderRadius.circular(16)
borderRadius: BorderRadius.circular(16), ),
child: TextFormField( child: ClipRRect(
controller: _searchController, borderRadius: BorderRadius.circular(16),
onChanged: (v) { child: TextFormField(
if (v == "") { controller: _searchController,
logsProvider.setSearchText(null); onChanged: (v) {
return; if (v == "") {
}
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); logsProvider.setSearchText(null);
}, return;
icon: const Icon( }
Icons.close_rounded, logsProvider.setSearchText(v);
size: 20, },
), onFieldSubmitted: (v) {
tooltip: AppLocalizations.of(context)!.clearSearch, widget.onSearch(v);
) Navigator.pop(context);
: null },
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
),
),
),
), ),
), ),
), ),
), )
), ],
) );
], }
), ),
), ),
); );

View file

@ -165,7 +165,7 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
processModal.open(AppLocalizations.of(context)!.savingConfig); processModal.open(AppLocalizations.of(context)!.savingConfig);
final result = await dnsProvider.saveDnsServerConfig({ final result = await dnsProvider.saveDnsServerConfig({
"ratelimit": int.parse(_limitRequestsController.text), "ratelimit": int.tryParse(_limitRequestsController.text),
"edns_cs_enabled": _enableEdns, "edns_cs_enabled": _enableEdns,
"edns_cs_use_custom": _useCustomIpEdns, "edns_cs_use_custom": _useCustomIpEdns,
"edns_cs_custom_ip": _customIpEdnsController.text, "edns_cs_custom_ip": _customIpEdnsController.text,

View file

@ -47,6 +47,7 @@ class _UpdateScreenState extends State<UpdateScreen> {
void processChangelog() async { void processChangelog() async {
final serversProvider = Provider.of<ServersProvider>(context, listen: false); final serversProvider = Provider.of<ServersProvider>(context, listen: false);
if (serversProvider.updateAvailable.data?.changelog == null) return;
final markdownResult = await compute(md.markdownToHtml, serversProvider.updateAvailable.data!.changelog!); final markdownResult = await compute(md.markdownToHtml, serversProvider.updateAvailable.data!.changelog!);
final htmlParsedResult = await compute(html.parse, markdownResult); final htmlParsedResult = await compute(html.parse, markdownResult);
setState(() => _htmlChangelog = htmlParsedResult.outerHtml); setState(() => _htmlChangelog = htmlParsedResult.outerHtml);
@ -84,7 +85,7 @@ class _UpdateScreenState extends State<UpdateScreen> {
processModal.close(); processModal.close();
if (!mounted) return; if (!context.mounted) return;
if (result.successful == true) { if (result.successful == true) {
serversProvider.recheckPeriodServerUpdated(); serversProvider.recheckPeriodServerUpdated();
showSnacbkar( showSnacbkar(

View file

@ -76,6 +76,7 @@ class _OptionsModal extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
contentPadding: const EdgeInsets.symmetric(vertical: 16), contentPadding: const EdgeInsets.symmetric(vertical: 16),
scrollable: true,
title: Column( title: Column(
children: [ children: [
Icon( Icon(
@ -94,19 +95,17 @@ class _OptionsModal extends StatelessWidget {
), ),
content: ConstrainedBox( content: ConstrainedBox(
constraints: const BoxConstraints( constraints: const BoxConstraints(
maxWidth: 400 maxWidth: 500
), ),
child: SingleChildScrollView( child: Column(
child: Wrap( children: options(value).map((opt) => CustomListTileDialog(
children: options(value).map((opt) => CustomListTileDialog( title: opt.title,
title: opt.title, icon: opt.icon,
icon: opt.icon, onTap: () {
onTap: () { Navigator.pop(context);
Navigator.pop(context); opt.action();
opt.action(); },
}, )).toList()
)).toList()
),
), ),
), ),
actions: [ actions: [

View file

@ -6,13 +6,13 @@ PODS:
- FlutterMacOS (1.0.0) - FlutterMacOS (1.0.0)
- package_info_plus (0.0.1): - package_info_plus (0.0.1):
- FlutterMacOS - FlutterMacOS
- Sentry/HybridSDK (8.20.0): - Sentry/HybridSDK (8.21.0):
- SentryPrivate (= 8.20.0) - SentryPrivate (= 8.21.0)
- sentry_flutter (0.0.1): - sentry_flutter (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- Sentry/HybridSDK (= 8.20.0) - Sentry/HybridSDK (= 8.21.0)
- SentryPrivate (8.20.0) - SentryPrivate (8.21.0)
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
@ -84,9 +84,9 @@ SPEC CHECKSUMS:
dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
Sentry: a8d7b373b9f9868442b02a0c425192f693103cbf Sentry: ebc12276bd17613a114ab359074096b6b3725203
sentry_flutter: 03e7660857a8cdb236e71456a7e8447b65c8a788 sentry_flutter: dff1df05dc39c83d04f9330b36360fc374574c5e
SentryPrivate: 006b24af16828441f70e2ab6adf241bd0a8ad130 SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
sqlite3: 73b7fc691fdc43277614250e04d183740cb15078 sqlite3: 73b7fc691fdc43277614250e04d183740cb15078

View file

@ -157,10 +157,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dynamic_color name: dynamic_color
sha256: a866f1f8947bfdaf674d7928e769eac7230388a2e7a2542824fad4bb5b87be3b sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.6.9" version: "1.7.0"
equatable: equatable:
dependency: transitive dependency: transitive
description: description:
@ -311,10 +311,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_markdown name: flutter_markdown
sha256: a64c5323ac83ed2b7940d2b6288d160aa1753ff271ba9d9b2a86770414aa3eab sha256: cb44f7831b23a6bdd0f501718b0d2e8045cbc625a15f668af37ddb80314821db
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.20+1" version: "0.6.21"
flutter_native_splash: flutter_native_splash:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -458,10 +458,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: markdown name: markdown
sha256: "1b134d9f8ff2da15cb298efe6cd8b7d2a78958c1b00384ebcbdf13fe340a6c90" sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.1" version: "7.2.2"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -618,18 +618,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sentry name: sentry
sha256: d2ee9c850d876d285f22e2e662f400ec2438df9939fe4acd5d780df9841794ce sha256: a524a87d096799b775530176c8c082afe7aa1f10cc31ba078fecdd74e9afc923
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.16.1" version: "7.17.0"
sentry_flutter: sentry_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: sentry_flutter name: sentry_flutter
sha256: "5b428c189c825f16fb14e9166529043f06b965d5b59bfc3a1415e39c082398c0" sha256: e0f8367f8f7c74dba9f7521f71700bce6c6ee065cf342f065d4fce411b84fc7b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.16.1" version: "7.17.0"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@ -951,10 +951,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.2.0" version: "5.3.0"
win32_registry: win32_registry:
dependency: transitive dependency: transitive
description: description:

View file

@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # 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 # 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. # of the product and file versions while build-number is used as the build suffix.
version: 2.16.4+134 version: 2.17.0+136
environment: environment:
sdk: '>=2.18.1 <3.0.0' sdk: '>=2.18.1 <3.0.0'