feat: per-tracker library whitelist/blacklist

close #917
This commit is contained in:
edde746
2026-04-24 10:30:16 +02:00
parent e8729a4e27
commit bf2584d90f
47 changed files with 1084 additions and 8 deletions
+14
View File
@@ -1046,6 +1046,20 @@
"body": "Scan denne QR-kode med din telefon, eller åbn URL'en nedenfor på en enhed med browser.",
"openToSignIn": "Åbn ${service} for at logge ind",
"urlCopied": "URL kopieret"
},
"libraryFilter": {
"title": "Bibliotekfilter",
"subtitleAllSyncing": "Synkroniserer alle biblioteker",
"subtitleNoneSyncing": "Intet synkroniseres",
"subtitleBlocked": "${count} blokeret",
"subtitleAllowed": "${count} tilladt",
"mode": "Filtertilstand",
"modeBlacklist": "Sortliste",
"modeWhitelist": "Hvidliste",
"modeHintBlacklist": "Synkroniser alle biblioteker undtagen dem du markerer nedenfor.",
"modeHintWhitelist": "Synkroniser kun de biblioteker du markerer nedenfor.",
"libraries": "Biblioteker",
"noLibraries": "Ingen biblioteker tilgængelige"
}
}
}
+14
View File
@@ -1046,6 +1046,20 @@
"body": "Scanne diesen QR-Code mit deinem Handy oder öffne die URL unten auf einem Gerät mit Browser.",
"openToSignIn": "${service} zum Anmelden öffnen",
"urlCopied": "URL kopiert"
},
"libraryFilter": {
"title": "Bibliothekfilter",
"subtitleAllSyncing": "Alle Bibliotheken werden synchronisiert",
"subtitleNoneSyncing": "Nichts wird synchronisiert",
"subtitleBlocked": "${count} blockiert",
"subtitleAllowed": "${count} erlaubt",
"mode": "Filtermodus",
"modeBlacklist": "Blacklist",
"modeWhitelist": "Whitelist",
"modeHintBlacklist": "Alle Bibliotheken außer den unten markierten synchronisieren.",
"modeHintWhitelist": "Nur die unten markierten Bibliotheken synchronisieren.",
"libraries": "Bibliotheken",
"noLibraries": "Keine Bibliotheken verfügbar"
}
}
}
+14
View File
@@ -1046,6 +1046,20 @@
"body": "Scan this QR code with your phone, or open the URL below on any device with a browser.",
"openToSignIn": "Open ${service} to sign in",
"urlCopied": "URL copied"
},
"libraryFilter": {
"title": "Library filter",
"subtitleAllSyncing": "Syncing all libraries",
"subtitleNoneSyncing": "Nothing syncing",
"subtitleBlocked": "${count} blocked",
"subtitleAllowed": "${count} allowed",
"mode": "Filter mode",
"modeBlacklist": "Blacklist",
"modeWhitelist": "Whitelist",
"modeHintBlacklist": "Sync every library except the ones checked below.",
"modeHintWhitelist": "Sync only the libraries checked below.",
"libraries": "Libraries",
"noLibraries": "No libraries available"
}
}
}
+14
View File
@@ -1046,6 +1046,20 @@
"body": "Escanea este código QR con tu teléfono, o abre la URL de abajo en cualquier dispositivo con navegador.",
"openToSignIn": "Abrir ${service} para iniciar sesión",
"urlCopied": "URL copiada"
},
"libraryFilter": {
"title": "Filtro de bibliotecas",
"subtitleAllSyncing": "Sincronizando todas las bibliotecas",
"subtitleNoneSyncing": "Nada se sincroniza",
"subtitleBlocked": "${count} bloqueadas",
"subtitleAllowed": "${count} permitidas",
"mode": "Modo de filtro",
"modeBlacklist": "Lista negra",
"modeWhitelist": "Lista blanca",
"modeHintBlacklist": "Sincronizar todas las bibliotecas excepto las marcadas abajo.",
"modeHintWhitelist": "Sincronizar solo las bibliotecas marcadas abajo.",
"libraries": "Bibliotecas",
"noLibraries": "No hay bibliotecas disponibles"
}
}
}
+14
View File
@@ -1046,6 +1046,20 @@
"body": "Scannez ce code QR avec votre téléphone, ou ouvrez l'URL ci-dessous sur un appareil équipé d'un navigateur.",
"openToSignIn": "Ouvrir ${service} pour se connecter",
"urlCopied": "URL copiée"
},
"libraryFilter": {
"title": "Filtre de bibliothèques",
"subtitleAllSyncing": "Synchronisation de toutes les bibliothèques",
"subtitleNoneSyncing": "Aucune synchronisation",
"subtitleBlocked": "${count} bloquées",
"subtitleAllowed": "${count} autorisées",
"mode": "Mode de filtrage",
"modeBlacklist": "Liste noire",
"modeWhitelist": "Liste blanche",
"modeHintBlacklist": "Synchroniser toutes les bibliothèques sauf celles cochées ci-dessous.",
"modeHintWhitelist": "Synchroniser uniquement les bibliothèques cochées ci-dessous.",
"libraries": "Bibliothèques",
"noLibraries": "Aucune bibliothèque disponible"
}
}
}
+14
View File
@@ -1046,6 +1046,20 @@
"body": "Scansiona questo codice QR con il telefono oppure apri l'URL sottostante su un dispositivo con browser.",
"openToSignIn": "Apri ${service} per accedere",
"urlCopied": "URL copiato"
},
"libraryFilter": {
"title": "Filtro librerie",
"subtitleAllSyncing": "Sincronizzazione di tutte le librerie",
"subtitleNoneSyncing": "Nulla da sincronizzare",
"subtitleBlocked": "${count} bloccate",
"subtitleAllowed": "${count} consentite",
"mode": "Modalità filtro",
"modeBlacklist": "Lista nera",
"modeWhitelist": "Lista bianca",
"modeHintBlacklist": "Sincronizza tutte le librerie tranne quelle selezionate sotto.",
"modeHintWhitelist": "Sincronizza solo le librerie selezionate sotto.",
"libraries": "Librerie",
"noLibraries": "Nessuna libreria disponibile"
}
}
}
+14
View File
@@ -1046,6 +1046,20 @@
"body": "このQRコードをスマートフォンで読み取るか、下のURLをブラウザで開いてください。",
"openToSignIn": "${service} を開いてサインイン",
"urlCopied": "URLをコピーしました"
},
"libraryFilter": {
"title": "ライブラリフィルター",
"subtitleAllSyncing": "すべてのライブラリを同期中",
"subtitleNoneSyncing": "同期なし",
"subtitleBlocked": "${count} 件をブロック",
"subtitleAllowed": "${count} 件を許可",
"mode": "フィルターモード",
"modeBlacklist": "ブロックリスト",
"modeWhitelist": "許可リスト",
"modeHintBlacklist": "下でチェックしたライブラリ以外をすべて同期します。",
"modeHintWhitelist": "下でチェックしたライブラリのみ同期します。",
"libraries": "ライブラリ",
"noLibraries": "利用できるライブラリがありません"
}
}
}
+14
View File
@@ -1046,6 +1046,20 @@
"body": "휴대전화로 이 QR 코드를 스캔하거나 아래 URL을 브라우저가 있는 기기에서 여세요.",
"openToSignIn": "로그인하려면 ${service} 열기",
"urlCopied": "URL이 복사되었습니다"
},
"libraryFilter": {
"title": "라이브러리 필터",
"subtitleAllSyncing": "모든 라이브러리 동기화 중",
"subtitleNoneSyncing": "동기화 안 함",
"subtitleBlocked": "${count}개 차단됨",
"subtitleAllowed": "${count}개 허용됨",
"mode": "필터 모드",
"modeBlacklist": "차단 목록",
"modeWhitelist": "허용 목록",
"modeHintBlacklist": "아래에 선택한 라이브러리를 제외한 모든 라이브러리를 동기화합니다.",
"modeHintWhitelist": "아래에 선택한 라이브러리만 동기화합니다.",
"libraries": "라이브러리",
"noLibraries": "사용 가능한 라이브러리가 없습니다"
}
}
}
+14
View File
@@ -1046,6 +1046,20 @@
"body": "Skann denne QR-koden med telefonen din, eller åpne URL-en under på en enhet med nettleser.",
"openToSignIn": "Åpne ${service} for å logge inn",
"urlCopied": "URL kopiert"
},
"libraryFilter": {
"title": "Biblioteksfilter",
"subtitleAllSyncing": "Synkroniserer alle biblioteker",
"subtitleNoneSyncing": "Ingenting synkroniseres",
"subtitleBlocked": "${count} blokkert",
"subtitleAllowed": "${count} tillatt",
"mode": "Filtermodus",
"modeBlacklist": "Svarteliste",
"modeWhitelist": "Hviteliste",
"modeHintBlacklist": "Synkroniser alle biblioteker bortsett fra dem du markerer nedenfor.",
"modeHintWhitelist": "Synkroniser kun bibliotekene du markerer nedenfor.",
"libraries": "Biblioteker",
"noLibraries": "Ingen biblioteker tilgjengelige"
}
}
}
+14
View File
@@ -1046,6 +1046,20 @@
"body": "Scan deze QR-code met je telefoon, of open onderstaande URL op een apparaat met browser.",
"openToSignIn": "${service} openen om aan te melden",
"urlCopied": "URL gekopieerd"
},
"libraryFilter": {
"title": "Bibliotheekfilter",
"subtitleAllSyncing": "Alle bibliotheken synchroniseren",
"subtitleNoneSyncing": "Niets wordt gesynchroniseerd",
"subtitleBlocked": "${count} geblokkeerd",
"subtitleAllowed": "${count} toegestaan",
"mode": "Filtermodus",
"modeBlacklist": "Zwarte lijst",
"modeWhitelist": "Witte lijst",
"modeHintBlacklist": "Synchroniseer alle bibliotheken behalve die hieronder aangevinkt zijn.",
"modeHintWhitelist": "Synchroniseer alleen de hieronder aangevinkte bibliotheken.",
"libraries": "Bibliotheken",
"noLibraries": "Geen bibliotheken beschikbaar"
}
}
}
+14
View File
@@ -1046,6 +1046,20 @@
"body": "Zeskanuj ten kod QR telefonem lub otwórz poniższy adres URL na dowolnym urządzeniu z przeglądarką.",
"openToSignIn": "Otwórz ${service}, aby się zalogować",
"urlCopied": "URL skopiowany"
},
"libraryFilter": {
"title": "Filtr bibliotek",
"subtitleAllSyncing": "Synchronizowanie wszystkich bibliotek",
"subtitleNoneSyncing": "Nic nie synchronizuje",
"subtitleBlocked": "${count} zablokowanych",
"subtitleAllowed": "${count} dozwolonych",
"mode": "Tryb filtra",
"modeBlacklist": "Czarna lista",
"modeWhitelist": "Biała lista",
"modeHintBlacklist": "Synchronizuj wszystkie biblioteki oprócz zaznaczonych poniżej.",
"modeHintWhitelist": "Synchronizuj tylko biblioteki zaznaczone poniżej.",
"libraries": "Biblioteki",
"noLibraries": "Brak dostępnych bibliotek"
}
}
}
+14
View File
@@ -1046,6 +1046,20 @@
"body": "Escaneie este código QR com o celular, ou abra o URL abaixo em qualquer dispositivo com navegador.",
"openToSignIn": "Abrir ${service} para entrar",
"urlCopied": "URL copiado"
},
"libraryFilter": {
"title": "Filtro de bibliotecas",
"subtitleAllSyncing": "Sincronizando todas as bibliotecas",
"subtitleNoneSyncing": "Nada a sincronizar",
"subtitleBlocked": "${count} bloqueadas",
"subtitleAllowed": "${count} permitidas",
"mode": "Modo de filtro",
"modeBlacklist": "Lista negra",
"modeWhitelist": "Lista branca",
"modeHintBlacklist": "Sincronizar todas as bibliotecas exceto as marcadas abaixo.",
"modeHintWhitelist": "Sincronizar apenas as bibliotecas marcadas abaixo.",
"libraries": "Bibliotecas",
"noLibraries": "Nenhuma biblioteca disponível"
}
}
}
+14
View File
@@ -1046,6 +1046,20 @@
"body": "Отсканируйте этот QR-код телефоном или откройте ссылку ниже на любом устройстве с браузером.",
"openToSignIn": "Открыть ${service} для входа",
"urlCopied": "URL скопирован"
},
"libraryFilter": {
"title": "Фильтр библиотек",
"subtitleAllSyncing": "Синхронизация всех библиотек",
"subtitleNoneSyncing": "Ничего не синхронизируется",
"subtitleBlocked": "${count} заблокировано",
"subtitleAllowed": "${count} разрешено",
"mode": "Режим фильтра",
"modeBlacklist": "Чёрный список",
"modeWhitelist": "Белый список",
"modeHintBlacklist": "Синхронизировать все библиотеки, кроме отмеченных ниже.",
"modeHintWhitelist": "Синхронизировать только библиотеки, отмеченные ниже.",
"libraries": "Библиотеки",
"noLibraries": "Библиотеки недоступны"
}
}
}
+2 -2
View File
@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 15
/// Strings: 14205 (947 per locale)
/// Strings: 14385 (959 per locale)
///
/// Built on 2026-04-24 at 07:00 UTC
/// Built on 2026-04-24 at 08:07 UTC
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import
+34
View File
@@ -1299,6 +1299,7 @@ class _TranslationsTrackersDa implements TranslationsTrackersEn {
@override late final _TranslationsTrackersServicesDa services = _TranslationsTrackersServicesDa._(_root);
@override late final _TranslationsTrackersDeviceCodeDa deviceCode = _TranslationsTrackersDeviceCodeDa._(_root);
@override late final _TranslationsTrackersOauthProxyDa oauthProxy = _TranslationsTrackersOauthProxyDa._(_root);
@override late final _TranslationsTrackersLibraryFilterDa libraryFilter = _TranslationsTrackersLibraryFilterDa._(_root);
}
// Path: hotkeys.actions
@@ -1496,6 +1497,27 @@ class _TranslationsTrackersOauthProxyDa implements TranslationsTrackersOauthProx
@override String get urlCopied => 'URL kopieret';
}
// Path: trackers.libraryFilter
class _TranslationsTrackersLibraryFilterDa implements TranslationsTrackersLibraryFilterEn {
_TranslationsTrackersLibraryFilterDa._(this._root);
final TranslationsDa _root; // ignore: unused_field
// Translations
@override String get title => 'Bibliotekfilter';
@override String get subtitleAllSyncing => 'Synkroniserer alle biblioteker';
@override String get subtitleNoneSyncing => 'Intet synkroniseres';
@override String subtitleBlocked({required Object count}) => '${count} blokeret';
@override String subtitleAllowed({required Object count}) => '${count} tilladt';
@override String get mode => 'Filtertilstand';
@override String get modeBlacklist => 'Sortliste';
@override String get modeWhitelist => 'Hvidliste';
@override String get modeHintBlacklist => 'Synkroniser alle biblioteker undtagen dem du markerer nedenfor.';
@override String get modeHintWhitelist => 'Synkroniser kun de biblioteker du markerer nedenfor.';
@override String get libraries => 'Biblioteker';
@override String get noLibraries => 'Ingen biblioteker tilgængelige';
}
/// The flat map containing all translations for locale <da>.
/// Only for edge cases! For simple maps, use the map function of this library.
///
@@ -2453,6 +2475,18 @@ extension on TranslationsDa {
'trackers.oauthProxy.body' => 'Scan denne QR-kode med din telefon, eller åbn URL\'en nedenfor på en enhed med browser.',
'trackers.oauthProxy.openToSignIn' => ({required Object service}) => 'Åbn ${service} for at logge ind',
'trackers.oauthProxy.urlCopied' => 'URL kopieret',
'trackers.libraryFilter.title' => 'Bibliotekfilter',
'trackers.libraryFilter.subtitleAllSyncing' => 'Synkroniserer alle biblioteker',
'trackers.libraryFilter.subtitleNoneSyncing' => 'Intet synkroniseres',
'trackers.libraryFilter.subtitleBlocked' => ({required Object count}) => '${count} blokeret',
'trackers.libraryFilter.subtitleAllowed' => ({required Object count}) => '${count} tilladt',
'trackers.libraryFilter.mode' => 'Filtertilstand',
'trackers.libraryFilter.modeBlacklist' => 'Sortliste',
'trackers.libraryFilter.modeWhitelist' => 'Hvidliste',
'trackers.libraryFilter.modeHintBlacklist' => 'Synkroniser alle biblioteker undtagen dem du markerer nedenfor.',
'trackers.libraryFilter.modeHintWhitelist' => 'Synkroniser kun de biblioteker du markerer nedenfor.',
'trackers.libraryFilter.libraries' => 'Biblioteker',
'trackers.libraryFilter.noLibraries' => 'Ingen biblioteker tilgængelige',
_ => null,
};
}
+34
View File
@@ -1299,6 +1299,7 @@ class _TranslationsTrackersDe implements TranslationsTrackersEn {
@override late final _TranslationsTrackersServicesDe services = _TranslationsTrackersServicesDe._(_root);
@override late final _TranslationsTrackersDeviceCodeDe deviceCode = _TranslationsTrackersDeviceCodeDe._(_root);
@override late final _TranslationsTrackersOauthProxyDe oauthProxy = _TranslationsTrackersOauthProxyDe._(_root);
@override late final _TranslationsTrackersLibraryFilterDe libraryFilter = _TranslationsTrackersLibraryFilterDe._(_root);
}
// Path: hotkeys.actions
@@ -1496,6 +1497,27 @@ class _TranslationsTrackersOauthProxyDe implements TranslationsTrackersOauthProx
@override String get urlCopied => 'URL kopiert';
}
// Path: trackers.libraryFilter
class _TranslationsTrackersLibraryFilterDe implements TranslationsTrackersLibraryFilterEn {
_TranslationsTrackersLibraryFilterDe._(this._root);
final TranslationsDe _root; // ignore: unused_field
// Translations
@override String get title => 'Bibliothekfilter';
@override String get subtitleAllSyncing => 'Alle Bibliotheken werden synchronisiert';
@override String get subtitleNoneSyncing => 'Nichts wird synchronisiert';
@override String subtitleBlocked({required Object count}) => '${count} blockiert';
@override String subtitleAllowed({required Object count}) => '${count} erlaubt';
@override String get mode => 'Filtermodus';
@override String get modeBlacklist => 'Blacklist';
@override String get modeWhitelist => 'Whitelist';
@override String get modeHintBlacklist => 'Alle Bibliotheken außer den unten markierten synchronisieren.';
@override String get modeHintWhitelist => 'Nur die unten markierten Bibliotheken synchronisieren.';
@override String get libraries => 'Bibliotheken';
@override String get noLibraries => 'Keine Bibliotheken verfügbar';
}
/// The flat map containing all translations for locale <de>.
/// Only for edge cases! For simple maps, use the map function of this library.
///
@@ -2453,6 +2475,18 @@ extension on TranslationsDe {
'trackers.oauthProxy.body' => 'Scanne diesen QR-Code mit deinem Handy oder öffne die URL unten auf einem Gerät mit Browser.',
'trackers.oauthProxy.openToSignIn' => ({required Object service}) => '${service} zum Anmelden öffnen',
'trackers.oauthProxy.urlCopied' => 'URL kopiert',
'trackers.libraryFilter.title' => 'Bibliothekfilter',
'trackers.libraryFilter.subtitleAllSyncing' => 'Alle Bibliotheken werden synchronisiert',
'trackers.libraryFilter.subtitleNoneSyncing' => 'Nichts wird synchronisiert',
'trackers.libraryFilter.subtitleBlocked' => ({required Object count}) => '${count} blockiert',
'trackers.libraryFilter.subtitleAllowed' => ({required Object count}) => '${count} erlaubt',
'trackers.libraryFilter.mode' => 'Filtermodus',
'trackers.libraryFilter.modeBlacklist' => 'Blacklist',
'trackers.libraryFilter.modeWhitelist' => 'Whitelist',
'trackers.libraryFilter.modeHintBlacklist' => 'Alle Bibliotheken außer den unten markierten synchronisieren.',
'trackers.libraryFilter.modeHintWhitelist' => 'Nur die unten markierten Bibliotheken synchronisieren.',
'trackers.libraryFilter.libraries' => 'Bibliotheken',
'trackers.libraryFilter.noLibraries' => 'Keine Bibliotheken verfügbar',
_ => null,
};
}
+58
View File
@@ -2991,6 +2991,7 @@ class TranslationsTrackersEn {
late final TranslationsTrackersServicesEn services = TranslationsTrackersServicesEn._(_root);
late final TranslationsTrackersDeviceCodeEn deviceCode = TranslationsTrackersDeviceCodeEn._(_root);
late final TranslationsTrackersOauthProxyEn oauthProxy = TranslationsTrackersOauthProxyEn._(_root);
late final TranslationsTrackersLibraryFilterEn libraryFilter = TranslationsTrackersLibraryFilterEn._(_root);
}
// Path: hotkeys.actions
@@ -3398,6 +3399,51 @@ class TranslationsTrackersOauthProxyEn {
String get urlCopied => 'URL copied';
}
// Path: trackers.libraryFilter
class TranslationsTrackersLibraryFilterEn {
TranslationsTrackersLibraryFilterEn._(this._root);
final Translations _root; // ignore: unused_field
// Translations
/// en: 'Library filter'
String get title => 'Library filter';
/// en: 'Syncing all libraries'
String get subtitleAllSyncing => 'Syncing all libraries';
/// en: 'Nothing syncing'
String get subtitleNoneSyncing => 'Nothing syncing';
/// en: '${count} blocked'
String subtitleBlocked({required Object count}) => '${count} blocked';
/// en: '${count} allowed'
String subtitleAllowed({required Object count}) => '${count} allowed';
/// en: 'Filter mode'
String get mode => 'Filter mode';
/// en: 'Blacklist'
String get modeBlacklist => 'Blacklist';
/// en: 'Whitelist'
String get modeWhitelist => 'Whitelist';
/// en: 'Sync every library except the ones checked below.'
String get modeHintBlacklist => 'Sync every library except the ones checked below.';
/// en: 'Sync only the libraries checked below.'
String get modeHintWhitelist => 'Sync only the libraries checked below.';
/// en: 'Libraries'
String get libraries => 'Libraries';
/// en: 'No libraries available'
String get noLibraries => 'No libraries available';
}
/// The flat map containing all translations for locale <en>.
/// Only for edge cases! For simple maps, use the map function of this library.
///
@@ -4355,6 +4401,18 @@ extension on Translations {
'trackers.oauthProxy.body' => 'Scan this QR code with your phone, or open the URL below on any device with a browser.',
'trackers.oauthProxy.openToSignIn' => ({required Object service}) => 'Open ${service} to sign in',
'trackers.oauthProxy.urlCopied' => 'URL copied',
'trackers.libraryFilter.title' => 'Library filter',
'trackers.libraryFilter.subtitleAllSyncing' => 'Syncing all libraries',
'trackers.libraryFilter.subtitleNoneSyncing' => 'Nothing syncing',
'trackers.libraryFilter.subtitleBlocked' => ({required Object count}) => '${count} blocked',
'trackers.libraryFilter.subtitleAllowed' => ({required Object count}) => '${count} allowed',
'trackers.libraryFilter.mode' => 'Filter mode',
'trackers.libraryFilter.modeBlacklist' => 'Blacklist',
'trackers.libraryFilter.modeWhitelist' => 'Whitelist',
'trackers.libraryFilter.modeHintBlacklist' => 'Sync every library except the ones checked below.',
'trackers.libraryFilter.modeHintWhitelist' => 'Sync only the libraries checked below.',
'trackers.libraryFilter.libraries' => 'Libraries',
'trackers.libraryFilter.noLibraries' => 'No libraries available',
_ => null,
};
}
+34
View File
@@ -1299,6 +1299,7 @@ class _TranslationsTrackersEs implements TranslationsTrackersEn {
@override late final _TranslationsTrackersServicesEs services = _TranslationsTrackersServicesEs._(_root);
@override late final _TranslationsTrackersDeviceCodeEs deviceCode = _TranslationsTrackersDeviceCodeEs._(_root);
@override late final _TranslationsTrackersOauthProxyEs oauthProxy = _TranslationsTrackersOauthProxyEs._(_root);
@override late final _TranslationsTrackersLibraryFilterEs libraryFilter = _TranslationsTrackersLibraryFilterEs._(_root);
}
// Path: hotkeys.actions
@@ -1496,6 +1497,27 @@ class _TranslationsTrackersOauthProxyEs implements TranslationsTrackersOauthProx
@override String get urlCopied => 'URL copiada';
}
// Path: trackers.libraryFilter
class _TranslationsTrackersLibraryFilterEs implements TranslationsTrackersLibraryFilterEn {
_TranslationsTrackersLibraryFilterEs._(this._root);
final TranslationsEs _root; // ignore: unused_field
// Translations
@override String get title => 'Filtro de bibliotecas';
@override String get subtitleAllSyncing => 'Sincronizando todas las bibliotecas';
@override String get subtitleNoneSyncing => 'Nada se sincroniza';
@override String subtitleBlocked({required Object count}) => '${count} bloqueadas';
@override String subtitleAllowed({required Object count}) => '${count} permitidas';
@override String get mode => 'Modo de filtro';
@override String get modeBlacklist => 'Lista negra';
@override String get modeWhitelist => 'Lista blanca';
@override String get modeHintBlacklist => 'Sincronizar todas las bibliotecas excepto las marcadas abajo.';
@override String get modeHintWhitelist => 'Sincronizar solo las bibliotecas marcadas abajo.';
@override String get libraries => 'Bibliotecas';
@override String get noLibraries => 'No hay bibliotecas disponibles';
}
/// The flat map containing all translations for locale <es>.
/// Only for edge cases! For simple maps, use the map function of this library.
///
@@ -2453,6 +2475,18 @@ extension on TranslationsEs {
'trackers.oauthProxy.body' => 'Escanea este código QR con tu teléfono, o abre la URL de abajo en cualquier dispositivo con navegador.',
'trackers.oauthProxy.openToSignIn' => ({required Object service}) => 'Abrir ${service} para iniciar sesión',
'trackers.oauthProxy.urlCopied' => 'URL copiada',
'trackers.libraryFilter.title' => 'Filtro de bibliotecas',
'trackers.libraryFilter.subtitleAllSyncing' => 'Sincronizando todas las bibliotecas',
'trackers.libraryFilter.subtitleNoneSyncing' => 'Nada se sincroniza',
'trackers.libraryFilter.subtitleBlocked' => ({required Object count}) => '${count} bloqueadas',
'trackers.libraryFilter.subtitleAllowed' => ({required Object count}) => '${count} permitidas',
'trackers.libraryFilter.mode' => 'Modo de filtro',
'trackers.libraryFilter.modeBlacklist' => 'Lista negra',
'trackers.libraryFilter.modeWhitelist' => 'Lista blanca',
'trackers.libraryFilter.modeHintBlacklist' => 'Sincronizar todas las bibliotecas excepto las marcadas abajo.',
'trackers.libraryFilter.modeHintWhitelist' => 'Sincronizar solo las bibliotecas marcadas abajo.',
'trackers.libraryFilter.libraries' => 'Bibliotecas',
'trackers.libraryFilter.noLibraries' => 'No hay bibliotecas disponibles',
_ => null,
};
}
+34
View File
@@ -1299,6 +1299,7 @@ class _TranslationsTrackersFr implements TranslationsTrackersEn {
@override late final _TranslationsTrackersServicesFr services = _TranslationsTrackersServicesFr._(_root);
@override late final _TranslationsTrackersDeviceCodeFr deviceCode = _TranslationsTrackersDeviceCodeFr._(_root);
@override late final _TranslationsTrackersOauthProxyFr oauthProxy = _TranslationsTrackersOauthProxyFr._(_root);
@override late final _TranslationsTrackersLibraryFilterFr libraryFilter = _TranslationsTrackersLibraryFilterFr._(_root);
}
// Path: hotkeys.actions
@@ -1496,6 +1497,27 @@ class _TranslationsTrackersOauthProxyFr implements TranslationsTrackersOauthProx
@override String get urlCopied => 'URL copiée';
}
// Path: trackers.libraryFilter
class _TranslationsTrackersLibraryFilterFr implements TranslationsTrackersLibraryFilterEn {
_TranslationsTrackersLibraryFilterFr._(this._root);
final TranslationsFr _root; // ignore: unused_field
// Translations
@override String get title => 'Filtre de bibliothèques';
@override String get subtitleAllSyncing => 'Synchronisation de toutes les bibliothèques';
@override String get subtitleNoneSyncing => 'Aucune synchronisation';
@override String subtitleBlocked({required Object count}) => '${count} bloquées';
@override String subtitleAllowed({required Object count}) => '${count} autorisées';
@override String get mode => 'Mode de filtrage';
@override String get modeBlacklist => 'Liste noire';
@override String get modeWhitelist => 'Liste blanche';
@override String get modeHintBlacklist => 'Synchroniser toutes les bibliothèques sauf celles cochées ci-dessous.';
@override String get modeHintWhitelist => 'Synchroniser uniquement les bibliothèques cochées ci-dessous.';
@override String get libraries => 'Bibliothèques';
@override String get noLibraries => 'Aucune bibliothèque disponible';
}
/// The flat map containing all translations for locale <fr>.
/// Only for edge cases! For simple maps, use the map function of this library.
///
@@ -2453,6 +2475,18 @@ extension on TranslationsFr {
'trackers.oauthProxy.body' => 'Scannez ce code QR avec votre téléphone, ou ouvrez l\'URL ci-dessous sur un appareil équipé d\'un navigateur.',
'trackers.oauthProxy.openToSignIn' => ({required Object service}) => 'Ouvrir ${service} pour se connecter',
'trackers.oauthProxy.urlCopied' => 'URL copiée',
'trackers.libraryFilter.title' => 'Filtre de bibliothèques',
'trackers.libraryFilter.subtitleAllSyncing' => 'Synchronisation de toutes les bibliothèques',
'trackers.libraryFilter.subtitleNoneSyncing' => 'Aucune synchronisation',
'trackers.libraryFilter.subtitleBlocked' => ({required Object count}) => '${count} bloquées',
'trackers.libraryFilter.subtitleAllowed' => ({required Object count}) => '${count} autorisées',
'trackers.libraryFilter.mode' => 'Mode de filtrage',
'trackers.libraryFilter.modeBlacklist' => 'Liste noire',
'trackers.libraryFilter.modeWhitelist' => 'Liste blanche',
'trackers.libraryFilter.modeHintBlacklist' => 'Synchroniser toutes les bibliothèques sauf celles cochées ci-dessous.',
'trackers.libraryFilter.modeHintWhitelist' => 'Synchroniser uniquement les bibliothèques cochées ci-dessous.',
'trackers.libraryFilter.libraries' => 'Bibliothèques',
'trackers.libraryFilter.noLibraries' => 'Aucune bibliothèque disponible',
_ => null,
};
}
+34
View File
@@ -1299,6 +1299,7 @@ class _TranslationsTrackersIt implements TranslationsTrackersEn {
@override late final _TranslationsTrackersServicesIt services = _TranslationsTrackersServicesIt._(_root);
@override late final _TranslationsTrackersDeviceCodeIt deviceCode = _TranslationsTrackersDeviceCodeIt._(_root);
@override late final _TranslationsTrackersOauthProxyIt oauthProxy = _TranslationsTrackersOauthProxyIt._(_root);
@override late final _TranslationsTrackersLibraryFilterIt libraryFilter = _TranslationsTrackersLibraryFilterIt._(_root);
}
// Path: hotkeys.actions
@@ -1496,6 +1497,27 @@ class _TranslationsTrackersOauthProxyIt implements TranslationsTrackersOauthProx
@override String get urlCopied => 'URL copiato';
}
// Path: trackers.libraryFilter
class _TranslationsTrackersLibraryFilterIt implements TranslationsTrackersLibraryFilterEn {
_TranslationsTrackersLibraryFilterIt._(this._root);
final TranslationsIt _root; // ignore: unused_field
// Translations
@override String get title => 'Filtro librerie';
@override String get subtitleAllSyncing => 'Sincronizzazione di tutte le librerie';
@override String get subtitleNoneSyncing => 'Nulla da sincronizzare';
@override String subtitleBlocked({required Object count}) => '${count} bloccate';
@override String subtitleAllowed({required Object count}) => '${count} consentite';
@override String get mode => 'Modalità filtro';
@override String get modeBlacklist => 'Lista nera';
@override String get modeWhitelist => 'Lista bianca';
@override String get modeHintBlacklist => 'Sincronizza tutte le librerie tranne quelle selezionate sotto.';
@override String get modeHintWhitelist => 'Sincronizza solo le librerie selezionate sotto.';
@override String get libraries => 'Librerie';
@override String get noLibraries => 'Nessuna libreria disponibile';
}
/// The flat map containing all translations for locale <it>.
/// Only for edge cases! For simple maps, use the map function of this library.
///
@@ -2453,6 +2475,18 @@ extension on TranslationsIt {
'trackers.oauthProxy.body' => 'Scansiona questo codice QR con il telefono oppure apri l\'URL sottostante su un dispositivo con browser.',
'trackers.oauthProxy.openToSignIn' => ({required Object service}) => 'Apri ${service} per accedere',
'trackers.oauthProxy.urlCopied' => 'URL copiato',
'trackers.libraryFilter.title' => 'Filtro librerie',
'trackers.libraryFilter.subtitleAllSyncing' => 'Sincronizzazione di tutte le librerie',
'trackers.libraryFilter.subtitleNoneSyncing' => 'Nulla da sincronizzare',
'trackers.libraryFilter.subtitleBlocked' => ({required Object count}) => '${count} bloccate',
'trackers.libraryFilter.subtitleAllowed' => ({required Object count}) => '${count} consentite',
'trackers.libraryFilter.mode' => 'Modalità filtro',
'trackers.libraryFilter.modeBlacklist' => 'Lista nera',
'trackers.libraryFilter.modeWhitelist' => 'Lista bianca',
'trackers.libraryFilter.modeHintBlacklist' => 'Sincronizza tutte le librerie tranne quelle selezionate sotto.',
'trackers.libraryFilter.modeHintWhitelist' => 'Sincronizza solo le librerie selezionate sotto.',
'trackers.libraryFilter.libraries' => 'Librerie',
'trackers.libraryFilter.noLibraries' => 'Nessuna libreria disponibile',
_ => null,
};
}
+34
View File
@@ -1299,6 +1299,7 @@ class _TranslationsTrackersJa implements TranslationsTrackersEn {
@override late final _TranslationsTrackersServicesJa services = _TranslationsTrackersServicesJa._(_root);
@override late final _TranslationsTrackersDeviceCodeJa deviceCode = _TranslationsTrackersDeviceCodeJa._(_root);
@override late final _TranslationsTrackersOauthProxyJa oauthProxy = _TranslationsTrackersOauthProxyJa._(_root);
@override late final _TranslationsTrackersLibraryFilterJa libraryFilter = _TranslationsTrackersLibraryFilterJa._(_root);
}
// Path: hotkeys.actions
@@ -1496,6 +1497,27 @@ class _TranslationsTrackersOauthProxyJa implements TranslationsTrackersOauthProx
@override String get urlCopied => 'URLをコピーしました';
}
// Path: trackers.libraryFilter
class _TranslationsTrackersLibraryFilterJa implements TranslationsTrackersLibraryFilterEn {
_TranslationsTrackersLibraryFilterJa._(this._root);
final TranslationsJa _root; // ignore: unused_field
// Translations
@override String get title => 'ライブラリフィルター';
@override String get subtitleAllSyncing => 'すべてのライブラリを同期中';
@override String get subtitleNoneSyncing => '同期なし';
@override String subtitleBlocked({required Object count}) => '${count} 件をブロック';
@override String subtitleAllowed({required Object count}) => '${count} 件を許可';
@override String get mode => 'フィルターモード';
@override String get modeBlacklist => 'ブロックリスト';
@override String get modeWhitelist => '許可リスト';
@override String get modeHintBlacklist => '下でチェックしたライブラリ以外をすべて同期します。';
@override String get modeHintWhitelist => '下でチェックしたライブラリのみ同期します。';
@override String get libraries => 'ライブラリ';
@override String get noLibraries => '利用できるライブラリがありません';
}
/// The flat map containing all translations for locale <ja>.
/// Only for edge cases! For simple maps, use the map function of this library.
///
@@ -2453,6 +2475,18 @@ extension on TranslationsJa {
'trackers.oauthProxy.body' => 'このQRコードをスマートフォンで読み取るか、下のURLをブラウザで開いてください。',
'trackers.oauthProxy.openToSignIn' => ({required Object service}) => '${service} を開いてサインイン',
'trackers.oauthProxy.urlCopied' => 'URLをコピーしました',
'trackers.libraryFilter.title' => 'ライブラリフィルター',
'trackers.libraryFilter.subtitleAllSyncing' => 'すべてのライブラリを同期中',
'trackers.libraryFilter.subtitleNoneSyncing' => '同期なし',
'trackers.libraryFilter.subtitleBlocked' => ({required Object count}) => '${count} 件をブロック',
'trackers.libraryFilter.subtitleAllowed' => ({required Object count}) => '${count} 件を許可',
'trackers.libraryFilter.mode' => 'フィルターモード',
'trackers.libraryFilter.modeBlacklist' => 'ブロックリスト',
'trackers.libraryFilter.modeWhitelist' => '許可リスト',
'trackers.libraryFilter.modeHintBlacklist' => '下でチェックしたライブラリ以外をすべて同期します。',
'trackers.libraryFilter.modeHintWhitelist' => '下でチェックしたライブラリのみ同期します。',
'trackers.libraryFilter.libraries' => 'ライブラリ',
'trackers.libraryFilter.noLibraries' => '利用できるライブラリがありません',
_ => null,
};
}
+34
View File
@@ -1299,6 +1299,7 @@ class _TranslationsTrackersKo implements TranslationsTrackersEn {
@override late final _TranslationsTrackersServicesKo services = _TranslationsTrackersServicesKo._(_root);
@override late final _TranslationsTrackersDeviceCodeKo deviceCode = _TranslationsTrackersDeviceCodeKo._(_root);
@override late final _TranslationsTrackersOauthProxyKo oauthProxy = _TranslationsTrackersOauthProxyKo._(_root);
@override late final _TranslationsTrackersLibraryFilterKo libraryFilter = _TranslationsTrackersLibraryFilterKo._(_root);
}
// Path: hotkeys.actions
@@ -1496,6 +1497,27 @@ class _TranslationsTrackersOauthProxyKo implements TranslationsTrackersOauthProx
@override String get urlCopied => 'URL이 복사되었습니다';
}
// Path: trackers.libraryFilter
class _TranslationsTrackersLibraryFilterKo implements TranslationsTrackersLibraryFilterEn {
_TranslationsTrackersLibraryFilterKo._(this._root);
final TranslationsKo _root; // ignore: unused_field
// Translations
@override String get title => '라이브러리 필터';
@override String get subtitleAllSyncing => '모든 라이브러리 동기화 중';
@override String get subtitleNoneSyncing => '동기화 안 함';
@override String subtitleBlocked({required Object count}) => '${count}개 차단됨';
@override String subtitleAllowed({required Object count}) => '${count}개 허용됨';
@override String get mode => '필터 모드';
@override String get modeBlacklist => '차단 목록';
@override String get modeWhitelist => '허용 목록';
@override String get modeHintBlacklist => '아래에 선택한 라이브러리를 제외한 모든 라이브러리를 동기화합니다.';
@override String get modeHintWhitelist => '아래에 선택한 라이브러리만 동기화합니다.';
@override String get libraries => '라이브러리';
@override String get noLibraries => '사용 가능한 라이브러리가 없습니다';
}
/// The flat map containing all translations for locale <ko>.
/// Only for edge cases! For simple maps, use the map function of this library.
///
@@ -2453,6 +2475,18 @@ extension on TranslationsKo {
'trackers.oauthProxy.body' => '휴대전화로 이 QR 코드를 스캔하거나 아래 URL을 브라우저가 있는 기기에서 여세요.',
'trackers.oauthProxy.openToSignIn' => ({required Object service}) => '로그인하려면 ${service} 열기',
'trackers.oauthProxy.urlCopied' => 'URL이 복사되었습니다',
'trackers.libraryFilter.title' => '라이브러리 필터',
'trackers.libraryFilter.subtitleAllSyncing' => '모든 라이브러리 동기화 중',
'trackers.libraryFilter.subtitleNoneSyncing' => '동기화 안 함',
'trackers.libraryFilter.subtitleBlocked' => ({required Object count}) => '${count}개 차단됨',
'trackers.libraryFilter.subtitleAllowed' => ({required Object count}) => '${count}개 허용됨',
'trackers.libraryFilter.mode' => '필터 모드',
'trackers.libraryFilter.modeBlacklist' => '차단 목록',
'trackers.libraryFilter.modeWhitelist' => '허용 목록',
'trackers.libraryFilter.modeHintBlacklist' => '아래에 선택한 라이브러리를 제외한 모든 라이브러리를 동기화합니다.',
'trackers.libraryFilter.modeHintWhitelist' => '아래에 선택한 라이브러리만 동기화합니다.',
'trackers.libraryFilter.libraries' => '라이브러리',
'trackers.libraryFilter.noLibraries' => '사용 가능한 라이브러리가 없습니다',
_ => null,
};
}
+34
View File
@@ -1299,6 +1299,7 @@ class _TranslationsTrackersNb implements TranslationsTrackersEn {
@override late final _TranslationsTrackersServicesNb services = _TranslationsTrackersServicesNb._(_root);
@override late final _TranslationsTrackersDeviceCodeNb deviceCode = _TranslationsTrackersDeviceCodeNb._(_root);
@override late final _TranslationsTrackersOauthProxyNb oauthProxy = _TranslationsTrackersOauthProxyNb._(_root);
@override late final _TranslationsTrackersLibraryFilterNb libraryFilter = _TranslationsTrackersLibraryFilterNb._(_root);
}
// Path: hotkeys.actions
@@ -1496,6 +1497,27 @@ class _TranslationsTrackersOauthProxyNb implements TranslationsTrackersOauthProx
@override String get urlCopied => 'URL kopiert';
}
// Path: trackers.libraryFilter
class _TranslationsTrackersLibraryFilterNb implements TranslationsTrackersLibraryFilterEn {
_TranslationsTrackersLibraryFilterNb._(this._root);
final TranslationsNb _root; // ignore: unused_field
// Translations
@override String get title => 'Biblioteksfilter';
@override String get subtitleAllSyncing => 'Synkroniserer alle biblioteker';
@override String get subtitleNoneSyncing => 'Ingenting synkroniseres';
@override String subtitleBlocked({required Object count}) => '${count} blokkert';
@override String subtitleAllowed({required Object count}) => '${count} tillatt';
@override String get mode => 'Filtermodus';
@override String get modeBlacklist => 'Svarteliste';
@override String get modeWhitelist => 'Hviteliste';
@override String get modeHintBlacklist => 'Synkroniser alle biblioteker bortsett fra dem du markerer nedenfor.';
@override String get modeHintWhitelist => 'Synkroniser kun bibliotekene du markerer nedenfor.';
@override String get libraries => 'Biblioteker';
@override String get noLibraries => 'Ingen biblioteker tilgjengelige';
}
/// The flat map containing all translations for locale <nb>.
/// Only for edge cases! For simple maps, use the map function of this library.
///
@@ -2453,6 +2475,18 @@ extension on TranslationsNb {
'trackers.oauthProxy.body' => 'Skann denne QR-koden med telefonen din, eller åpne URL-en under på en enhet med nettleser.',
'trackers.oauthProxy.openToSignIn' => ({required Object service}) => 'Åpne ${service} for å logge inn',
'trackers.oauthProxy.urlCopied' => 'URL kopiert',
'trackers.libraryFilter.title' => 'Biblioteksfilter',
'trackers.libraryFilter.subtitleAllSyncing' => 'Synkroniserer alle biblioteker',
'trackers.libraryFilter.subtitleNoneSyncing' => 'Ingenting synkroniseres',
'trackers.libraryFilter.subtitleBlocked' => ({required Object count}) => '${count} blokkert',
'trackers.libraryFilter.subtitleAllowed' => ({required Object count}) => '${count} tillatt',
'trackers.libraryFilter.mode' => 'Filtermodus',
'trackers.libraryFilter.modeBlacklist' => 'Svarteliste',
'trackers.libraryFilter.modeWhitelist' => 'Hviteliste',
'trackers.libraryFilter.modeHintBlacklist' => 'Synkroniser alle biblioteker bortsett fra dem du markerer nedenfor.',
'trackers.libraryFilter.modeHintWhitelist' => 'Synkroniser kun bibliotekene du markerer nedenfor.',
'trackers.libraryFilter.libraries' => 'Biblioteker',
'trackers.libraryFilter.noLibraries' => 'Ingen biblioteker tilgjengelige',
_ => null,
};
}
+34
View File
@@ -1299,6 +1299,7 @@ class _TranslationsTrackersNl implements TranslationsTrackersEn {
@override late final _TranslationsTrackersServicesNl services = _TranslationsTrackersServicesNl._(_root);
@override late final _TranslationsTrackersDeviceCodeNl deviceCode = _TranslationsTrackersDeviceCodeNl._(_root);
@override late final _TranslationsTrackersOauthProxyNl oauthProxy = _TranslationsTrackersOauthProxyNl._(_root);
@override late final _TranslationsTrackersLibraryFilterNl libraryFilter = _TranslationsTrackersLibraryFilterNl._(_root);
}
// Path: hotkeys.actions
@@ -1496,6 +1497,27 @@ class _TranslationsTrackersOauthProxyNl implements TranslationsTrackersOauthProx
@override String get urlCopied => 'URL gekopieerd';
}
// Path: trackers.libraryFilter
class _TranslationsTrackersLibraryFilterNl implements TranslationsTrackersLibraryFilterEn {
_TranslationsTrackersLibraryFilterNl._(this._root);
final TranslationsNl _root; // ignore: unused_field
// Translations
@override String get title => 'Bibliotheekfilter';
@override String get subtitleAllSyncing => 'Alle bibliotheken synchroniseren';
@override String get subtitleNoneSyncing => 'Niets wordt gesynchroniseerd';
@override String subtitleBlocked({required Object count}) => '${count} geblokkeerd';
@override String subtitleAllowed({required Object count}) => '${count} toegestaan';
@override String get mode => 'Filtermodus';
@override String get modeBlacklist => 'Zwarte lijst';
@override String get modeWhitelist => 'Witte lijst';
@override String get modeHintBlacklist => 'Synchroniseer alle bibliotheken behalve die hieronder aangevinkt zijn.';
@override String get modeHintWhitelist => 'Synchroniseer alleen de hieronder aangevinkte bibliotheken.';
@override String get libraries => 'Bibliotheken';
@override String get noLibraries => 'Geen bibliotheken beschikbaar';
}
/// The flat map containing all translations for locale <nl>.
/// Only for edge cases! For simple maps, use the map function of this library.
///
@@ -2453,6 +2475,18 @@ extension on TranslationsNl {
'trackers.oauthProxy.body' => 'Scan deze QR-code met je telefoon, of open onderstaande URL op een apparaat met browser.',
'trackers.oauthProxy.openToSignIn' => ({required Object service}) => '${service} openen om aan te melden',
'trackers.oauthProxy.urlCopied' => 'URL gekopieerd',
'trackers.libraryFilter.title' => 'Bibliotheekfilter',
'trackers.libraryFilter.subtitleAllSyncing' => 'Alle bibliotheken synchroniseren',
'trackers.libraryFilter.subtitleNoneSyncing' => 'Niets wordt gesynchroniseerd',
'trackers.libraryFilter.subtitleBlocked' => ({required Object count}) => '${count} geblokkeerd',
'trackers.libraryFilter.subtitleAllowed' => ({required Object count}) => '${count} toegestaan',
'trackers.libraryFilter.mode' => 'Filtermodus',
'trackers.libraryFilter.modeBlacklist' => 'Zwarte lijst',
'trackers.libraryFilter.modeWhitelist' => 'Witte lijst',
'trackers.libraryFilter.modeHintBlacklist' => 'Synchroniseer alle bibliotheken behalve die hieronder aangevinkt zijn.',
'trackers.libraryFilter.modeHintWhitelist' => 'Synchroniseer alleen de hieronder aangevinkte bibliotheken.',
'trackers.libraryFilter.libraries' => 'Bibliotheken',
'trackers.libraryFilter.noLibraries' => 'Geen bibliotheken beschikbaar',
_ => null,
};
}
+34
View File
@@ -1299,6 +1299,7 @@ class _TranslationsTrackersPl implements TranslationsTrackersEn {
@override late final _TranslationsTrackersServicesPl services = _TranslationsTrackersServicesPl._(_root);
@override late final _TranslationsTrackersDeviceCodePl deviceCode = _TranslationsTrackersDeviceCodePl._(_root);
@override late final _TranslationsTrackersOauthProxyPl oauthProxy = _TranslationsTrackersOauthProxyPl._(_root);
@override late final _TranslationsTrackersLibraryFilterPl libraryFilter = _TranslationsTrackersLibraryFilterPl._(_root);
}
// Path: hotkeys.actions
@@ -1496,6 +1497,27 @@ class _TranslationsTrackersOauthProxyPl implements TranslationsTrackersOauthProx
@override String get urlCopied => 'URL skopiowany';
}
// Path: trackers.libraryFilter
class _TranslationsTrackersLibraryFilterPl implements TranslationsTrackersLibraryFilterEn {
_TranslationsTrackersLibraryFilterPl._(this._root);
final TranslationsPl _root; // ignore: unused_field
// Translations
@override String get title => 'Filtr bibliotek';
@override String get subtitleAllSyncing => 'Synchronizowanie wszystkich bibliotek';
@override String get subtitleNoneSyncing => 'Nic nie synchronizuje';
@override String subtitleBlocked({required Object count}) => '${count} zablokowanych';
@override String subtitleAllowed({required Object count}) => '${count} dozwolonych';
@override String get mode => 'Tryb filtra';
@override String get modeBlacklist => 'Czarna lista';
@override String get modeWhitelist => 'Biała lista';
@override String get modeHintBlacklist => 'Synchronizuj wszystkie biblioteki oprócz zaznaczonych poniżej.';
@override String get modeHintWhitelist => 'Synchronizuj tylko biblioteki zaznaczone poniżej.';
@override String get libraries => 'Biblioteki';
@override String get noLibraries => 'Brak dostępnych bibliotek';
}
/// The flat map containing all translations for locale <pl>.
/// Only for edge cases! For simple maps, use the map function of this library.
///
@@ -2453,6 +2475,18 @@ extension on TranslationsPl {
'trackers.oauthProxy.body' => 'Zeskanuj ten kod QR telefonem lub otwórz poniższy adres URL na dowolnym urządzeniu z przeglądarką.',
'trackers.oauthProxy.openToSignIn' => ({required Object service}) => 'Otwórz ${service}, aby się zalogować',
'trackers.oauthProxy.urlCopied' => 'URL skopiowany',
'trackers.libraryFilter.title' => 'Filtr bibliotek',
'trackers.libraryFilter.subtitleAllSyncing' => 'Synchronizowanie wszystkich bibliotek',
'trackers.libraryFilter.subtitleNoneSyncing' => 'Nic nie synchronizuje',
'trackers.libraryFilter.subtitleBlocked' => ({required Object count}) => '${count} zablokowanych',
'trackers.libraryFilter.subtitleAllowed' => ({required Object count}) => '${count} dozwolonych',
'trackers.libraryFilter.mode' => 'Tryb filtra',
'trackers.libraryFilter.modeBlacklist' => 'Czarna lista',
'trackers.libraryFilter.modeWhitelist' => 'Biała lista',
'trackers.libraryFilter.modeHintBlacklist' => 'Synchronizuj wszystkie biblioteki oprócz zaznaczonych poniżej.',
'trackers.libraryFilter.modeHintWhitelist' => 'Synchronizuj tylko biblioteki zaznaczone poniżej.',
'trackers.libraryFilter.libraries' => 'Biblioteki',
'trackers.libraryFilter.noLibraries' => 'Brak dostępnych bibliotek',
_ => null,
};
}
+34
View File
@@ -1299,6 +1299,7 @@ class _TranslationsTrackersPt implements TranslationsTrackersEn {
@override late final _TranslationsTrackersServicesPt services = _TranslationsTrackersServicesPt._(_root);
@override late final _TranslationsTrackersDeviceCodePt deviceCode = _TranslationsTrackersDeviceCodePt._(_root);
@override late final _TranslationsTrackersOauthProxyPt oauthProxy = _TranslationsTrackersOauthProxyPt._(_root);
@override late final _TranslationsTrackersLibraryFilterPt libraryFilter = _TranslationsTrackersLibraryFilterPt._(_root);
}
// Path: hotkeys.actions
@@ -1496,6 +1497,27 @@ class _TranslationsTrackersOauthProxyPt implements TranslationsTrackersOauthProx
@override String get urlCopied => 'URL copiado';
}
// Path: trackers.libraryFilter
class _TranslationsTrackersLibraryFilterPt implements TranslationsTrackersLibraryFilterEn {
_TranslationsTrackersLibraryFilterPt._(this._root);
final TranslationsPt _root; // ignore: unused_field
// Translations
@override String get title => 'Filtro de bibliotecas';
@override String get subtitleAllSyncing => 'Sincronizando todas as bibliotecas';
@override String get subtitleNoneSyncing => 'Nada a sincronizar';
@override String subtitleBlocked({required Object count}) => '${count} bloqueadas';
@override String subtitleAllowed({required Object count}) => '${count} permitidas';
@override String get mode => 'Modo de filtro';
@override String get modeBlacklist => 'Lista negra';
@override String get modeWhitelist => 'Lista branca';
@override String get modeHintBlacklist => 'Sincronizar todas as bibliotecas exceto as marcadas abaixo.';
@override String get modeHintWhitelist => 'Sincronizar apenas as bibliotecas marcadas abaixo.';
@override String get libraries => 'Bibliotecas';
@override String get noLibraries => 'Nenhuma biblioteca disponível';
}
/// The flat map containing all translations for locale <pt>.
/// Only for edge cases! For simple maps, use the map function of this library.
///
@@ -2453,6 +2475,18 @@ extension on TranslationsPt {
'trackers.oauthProxy.body' => 'Escaneie este código QR com o celular, ou abra o URL abaixo em qualquer dispositivo com navegador.',
'trackers.oauthProxy.openToSignIn' => ({required Object service}) => 'Abrir ${service} para entrar',
'trackers.oauthProxy.urlCopied' => 'URL copiado',
'trackers.libraryFilter.title' => 'Filtro de bibliotecas',
'trackers.libraryFilter.subtitleAllSyncing' => 'Sincronizando todas as bibliotecas',
'trackers.libraryFilter.subtitleNoneSyncing' => 'Nada a sincronizar',
'trackers.libraryFilter.subtitleBlocked' => ({required Object count}) => '${count} bloqueadas',
'trackers.libraryFilter.subtitleAllowed' => ({required Object count}) => '${count} permitidas',
'trackers.libraryFilter.mode' => 'Modo de filtro',
'trackers.libraryFilter.modeBlacklist' => 'Lista negra',
'trackers.libraryFilter.modeWhitelist' => 'Lista branca',
'trackers.libraryFilter.modeHintBlacklist' => 'Sincronizar todas as bibliotecas exceto as marcadas abaixo.',
'trackers.libraryFilter.modeHintWhitelist' => 'Sincronizar apenas as bibliotecas marcadas abaixo.',
'trackers.libraryFilter.libraries' => 'Bibliotecas',
'trackers.libraryFilter.noLibraries' => 'Nenhuma biblioteca disponível',
_ => null,
};
}
+34
View File
@@ -1299,6 +1299,7 @@ class _TranslationsTrackersRu implements TranslationsTrackersEn {
@override late final _TranslationsTrackersServicesRu services = _TranslationsTrackersServicesRu._(_root);
@override late final _TranslationsTrackersDeviceCodeRu deviceCode = _TranslationsTrackersDeviceCodeRu._(_root);
@override late final _TranslationsTrackersOauthProxyRu oauthProxy = _TranslationsTrackersOauthProxyRu._(_root);
@override late final _TranslationsTrackersLibraryFilterRu libraryFilter = _TranslationsTrackersLibraryFilterRu._(_root);
}
// Path: hotkeys.actions
@@ -1496,6 +1497,27 @@ class _TranslationsTrackersOauthProxyRu implements TranslationsTrackersOauthProx
@override String get urlCopied => 'URL скопирован';
}
// Path: trackers.libraryFilter
class _TranslationsTrackersLibraryFilterRu implements TranslationsTrackersLibraryFilterEn {
_TranslationsTrackersLibraryFilterRu._(this._root);
final TranslationsRu _root; // ignore: unused_field
// Translations
@override String get title => 'Фильтр библиотек';
@override String get subtitleAllSyncing => 'Синхронизация всех библиотек';
@override String get subtitleNoneSyncing => 'Ничего не синхронизируется';
@override String subtitleBlocked({required Object count}) => '${count} заблокировано';
@override String subtitleAllowed({required Object count}) => '${count} разрешено';
@override String get mode => 'Режим фильтра';
@override String get modeBlacklist => 'Чёрный список';
@override String get modeWhitelist => 'Белый список';
@override String get modeHintBlacklist => 'Синхронизировать все библиотеки, кроме отмеченных ниже.';
@override String get modeHintWhitelist => 'Синхронизировать только библиотеки, отмеченные ниже.';
@override String get libraries => 'Библиотеки';
@override String get noLibraries => 'Библиотеки недоступны';
}
/// The flat map containing all translations for locale <ru>.
/// Only for edge cases! For simple maps, use the map function of this library.
///
@@ -2453,6 +2475,18 @@ extension on TranslationsRu {
'trackers.oauthProxy.body' => 'Отсканируйте этот QR-код телефоном или откройте ссылку ниже на любом устройстве с браузером.',
'trackers.oauthProxy.openToSignIn' => ({required Object service}) => 'Открыть ${service} для входа',
'trackers.oauthProxy.urlCopied' => 'URL скопирован',
'trackers.libraryFilter.title' => 'Фильтр библиотек',
'trackers.libraryFilter.subtitleAllSyncing' => 'Синхронизация всех библиотек',
'trackers.libraryFilter.subtitleNoneSyncing' => 'Ничего не синхронизируется',
'trackers.libraryFilter.subtitleBlocked' => ({required Object count}) => '${count} заблокировано',
'trackers.libraryFilter.subtitleAllowed' => ({required Object count}) => '${count} разрешено',
'trackers.libraryFilter.mode' => 'Режим фильтра',
'trackers.libraryFilter.modeBlacklist' => 'Чёрный список',
'trackers.libraryFilter.modeWhitelist' => 'Белый список',
'trackers.libraryFilter.modeHintBlacklist' => 'Синхронизировать все библиотеки, кроме отмеченных ниже.',
'trackers.libraryFilter.modeHintWhitelist' => 'Синхронизировать только библиотеки, отмеченные ниже.',
'trackers.libraryFilter.libraries' => 'Библиотеки',
'trackers.libraryFilter.noLibraries' => 'Библиотеки недоступны',
_ => null,
};
}
+34
View File
@@ -1299,6 +1299,7 @@ class _TranslationsTrackersSv implements TranslationsTrackersEn {
@override late final _TranslationsTrackersServicesSv services = _TranslationsTrackersServicesSv._(_root);
@override late final _TranslationsTrackersDeviceCodeSv deviceCode = _TranslationsTrackersDeviceCodeSv._(_root);
@override late final _TranslationsTrackersOauthProxySv oauthProxy = _TranslationsTrackersOauthProxySv._(_root);
@override late final _TranslationsTrackersLibraryFilterSv libraryFilter = _TranslationsTrackersLibraryFilterSv._(_root);
}
// Path: hotkeys.actions
@@ -1496,6 +1497,27 @@ class _TranslationsTrackersOauthProxySv implements TranslationsTrackersOauthProx
@override String get urlCopied => 'URL kopierad';
}
// Path: trackers.libraryFilter
class _TranslationsTrackersLibraryFilterSv implements TranslationsTrackersLibraryFilterEn {
_TranslationsTrackersLibraryFilterSv._(this._root);
final TranslationsSv _root; // ignore: unused_field
// Translations
@override String get title => 'Biblioteksfilter';
@override String get subtitleAllSyncing => 'Synkroniserar alla bibliotek';
@override String get subtitleNoneSyncing => 'Ingenting synkroniseras';
@override String subtitleBlocked({required Object count}) => '${count} blockerade';
@override String subtitleAllowed({required Object count}) => '${count} tillåtna';
@override String get mode => 'Filterläge';
@override String get modeBlacklist => 'Svartlista';
@override String get modeWhitelist => 'Vitlista';
@override String get modeHintBlacklist => 'Synkronisera alla bibliotek utom de som markerats nedan.';
@override String get modeHintWhitelist => 'Synkronisera endast de bibliotek som markerats nedan.';
@override String get libraries => 'Bibliotek';
@override String get noLibraries => 'Inga bibliotek tillgängliga';
}
/// The flat map containing all translations for locale <sv>.
/// Only for edge cases! For simple maps, use the map function of this library.
///
@@ -2453,6 +2475,18 @@ extension on TranslationsSv {
'trackers.oauthProxy.body' => 'Skanna den här QR-koden med din telefon, eller öppna URL:en nedan på en enhet med webbläsare.',
'trackers.oauthProxy.openToSignIn' => ({required Object service}) => 'Öppna ${service} för att logga in',
'trackers.oauthProxy.urlCopied' => 'URL kopierad',
'trackers.libraryFilter.title' => 'Biblioteksfilter',
'trackers.libraryFilter.subtitleAllSyncing' => 'Synkroniserar alla bibliotek',
'trackers.libraryFilter.subtitleNoneSyncing' => 'Ingenting synkroniseras',
'trackers.libraryFilter.subtitleBlocked' => ({required Object count}) => '${count} blockerade',
'trackers.libraryFilter.subtitleAllowed' => ({required Object count}) => '${count} tillåtna',
'trackers.libraryFilter.mode' => 'Filterläge',
'trackers.libraryFilter.modeBlacklist' => 'Svartlista',
'trackers.libraryFilter.modeWhitelist' => 'Vitlista',
'trackers.libraryFilter.modeHintBlacklist' => 'Synkronisera alla bibliotek utom de som markerats nedan.',
'trackers.libraryFilter.modeHintWhitelist' => 'Synkronisera endast de bibliotek som markerats nedan.',
'trackers.libraryFilter.libraries' => 'Bibliotek',
'trackers.libraryFilter.noLibraries' => 'Inga bibliotek tillgängliga',
_ => null,
};
}
+34
View File
@@ -1299,6 +1299,7 @@ class _TranslationsTrackersZh implements TranslationsTrackersEn {
@override late final _TranslationsTrackersServicesZh services = _TranslationsTrackersServicesZh._(_root);
@override late final _TranslationsTrackersDeviceCodeZh deviceCode = _TranslationsTrackersDeviceCodeZh._(_root);
@override late final _TranslationsTrackersOauthProxyZh oauthProxy = _TranslationsTrackersOauthProxyZh._(_root);
@override late final _TranslationsTrackersLibraryFilterZh libraryFilter = _TranslationsTrackersLibraryFilterZh._(_root);
}
// Path: hotkeys.actions
@@ -1496,6 +1497,27 @@ class _TranslationsTrackersOauthProxyZh implements TranslationsTrackersOauthProx
@override String get urlCopied => 'URL 已复制';
}
// Path: trackers.libraryFilter
class _TranslationsTrackersLibraryFilterZh implements TranslationsTrackersLibraryFilterEn {
_TranslationsTrackersLibraryFilterZh._(this._root);
final TranslationsZh _root; // ignore: unused_field
// Translations
@override String get title => '媒体库筛选';
@override String get subtitleAllSyncing => '同步所有媒体库';
@override String get subtitleNoneSyncing => '不同步任何内容';
@override String subtitleBlocked({required Object count}) => '已屏蔽 ${count}';
@override String subtitleAllowed({required Object count}) => '已允许 ${count}';
@override String get mode => '筛选模式';
@override String get modeBlacklist => '黑名单';
@override String get modeWhitelist => '白名单';
@override String get modeHintBlacklist => '同步下方未勾选的所有媒体库。';
@override String get modeHintWhitelist => '仅同步下方勾选的媒体库。';
@override String get libraries => '媒体库';
@override String get noLibraries => '没有可用的媒体库';
}
/// The flat map containing all translations for locale <zh>.
/// Only for edge cases! For simple maps, use the map function of this library.
///
@@ -2453,6 +2475,18 @@ extension on TranslationsZh {
'trackers.oauthProxy.body' => '用手机扫描此二维码,或在任何带浏览器的设备上打开下方 URL。',
'trackers.oauthProxy.openToSignIn' => ({required Object service}) => '打开 ${service} 以登录',
'trackers.oauthProxy.urlCopied' => 'URL 已复制',
'trackers.libraryFilter.title' => '媒体库筛选',
'trackers.libraryFilter.subtitleAllSyncing' => '同步所有媒体库',
'trackers.libraryFilter.subtitleNoneSyncing' => '不同步任何内容',
'trackers.libraryFilter.subtitleBlocked' => ({required Object count}) => '已屏蔽 ${count}',
'trackers.libraryFilter.subtitleAllowed' => ({required Object count}) => '已允许 ${count}',
'trackers.libraryFilter.mode' => '筛选模式',
'trackers.libraryFilter.modeBlacklist' => '黑名单',
'trackers.libraryFilter.modeWhitelist' => '白名单',
'trackers.libraryFilter.modeHintBlacklist' => '同步下方未勾选的所有媒体库。',
'trackers.libraryFilter.modeHintWhitelist' => '仅同步下方勾选的媒体库。',
'trackers.libraryFilter.libraries' => '媒体库',
'trackers.libraryFilter.noLibraries' => '没有可用的媒体库',
_ => null,
};
}
+14
View File
@@ -1046,6 +1046,20 @@
"body": "Skanna den här QR-koden med din telefon, eller öppna URL:en nedan på en enhet med webbläsare.",
"openToSignIn": "Öppna ${service} för att logga in",
"urlCopied": "URL kopierad"
},
"libraryFilter": {
"title": "Biblioteksfilter",
"subtitleAllSyncing": "Synkroniserar alla bibliotek",
"subtitleNoneSyncing": "Ingenting synkroniseras",
"subtitleBlocked": "${count} blockerade",
"subtitleAllowed": "${count} tillåtna",
"mode": "Filterläge",
"modeBlacklist": "Svartlista",
"modeWhitelist": "Vitlista",
"modeHintBlacklist": "Synkronisera alla bibliotek utom de som markerats nedan.",
"modeHintWhitelist": "Synkronisera endast de bibliotek som markerats nedan.",
"libraries": "Bibliotek",
"noLibraries": "Inga bibliotek tillgängliga"
}
}
}
+14
View File
@@ -1046,6 +1046,20 @@
"body": "用手机扫描此二维码,或在任何带浏览器的设备上打开下方 URL。",
"openToSignIn": "打开 ${service} 以登录",
"urlCopied": "URL 已复制"
},
"libraryFilter": {
"title": "媒体库筛选",
"subtitleAllSyncing": "同步所有媒体库",
"subtitleNoneSyncing": "不同步任何内容",
"subtitleBlocked": "已屏蔽 ${count} 个",
"subtitleAllowed": "已允许 ${count} 个",
"mode": "筛选模式",
"modeBlacklist": "黑名单",
"modeWhitelist": "白名单",
"modeHintBlacklist": "同步下方未勾选的所有媒体库。",
"modeHintWhitelist": "仅同步下方勾选的媒体库。",
"libraries": "媒体库",
"noLibraries": "没有可用的媒体库"
}
}
}
+6
View File
@@ -154,6 +154,12 @@ class PlexMetadata with MultiServerFields {
/// Global unique identifier across all servers (serverId:ratingKey)
String get globalKey => serverId != null ? buildGlobalKey(serverId!, ratingKey) : ratingKey;
/// Global unique identifier of this item's library section, matching
/// [PlexLibrary.globalKey]. Null when either [serverId] or [librarySectionID]
/// is missing.
String? get librarySectionGlobalKey =>
serverId != null && librarySectionID != null ? buildGlobalKey(serverId!, librarySectionID!.toString()) : null;
/// Parent rating keys for hierarchical invalidation.
/// For an episode: [seasonRatingKey, showRatingKey]
/// For a season: [showRatingKey]
+15 -1
View File
@@ -20,11 +20,16 @@ class TrackerContext {
/// sent to any tracker.
final String ratingKey;
/// Library globalKey the item belongs to, or null when the metadata didn't
/// carry library info.
final String? libraryGlobalKey;
const TrackerContext._({
required this.external,
required this.anime,
required this.isMovie,
required this.ratingKey,
required this.libraryGlobalKey,
this.season,
this.episodeNumber,
});
@@ -33,14 +38,22 @@ class TrackerContext {
required PlexExternalIds external,
required AnimeIds? anime,
required String ratingKey,
required String? libraryGlobalKey,
}) {
return TrackerContext._(external: external, anime: anime, isMovie: true, ratingKey: ratingKey);
return TrackerContext._(
external: external,
anime: anime,
isMovie: true,
ratingKey: ratingKey,
libraryGlobalKey: libraryGlobalKey,
);
}
factory TrackerContext.episode({
required PlexExternalIds external,
required AnimeIds? anime,
required String ratingKey,
required String? libraryGlobalKey,
required int season,
required int episodeNumber,
}) {
@@ -49,6 +62,7 @@ class TrackerContext {
anime: anime,
isMovie: false,
ratingKey: ratingKey,
libraryGlobalKey: libraryGlobalKey,
season: season,
episodeNumber: episodeNumber,
);
+1 -4
View File
@@ -20,13 +20,10 @@ import '../services/trackers/simkl/simkl_client.dart';
import '../services/trackers/simkl/simkl_session.dart';
import '../services/trackers/simkl/simkl_tracker.dart';
import '../services/trackers/tracker_account_store.dart';
import '../services/trackers/tracker_constants.dart';
import '../services/trackers/tracker_coordinator.dart';
import '../utils/app_logger.dart';
/// Identifier used in the settings UI to disambiguate disconnect/connect
/// actions without importing the per-service session classes into widgets.
enum TrackerService { mal, anilist, simkl }
/// Owns the active MAL / AniList / Simkl sessions for the currently-selected
/// Plex profile. Single rebind seam: [onActiveProfileChanged] loads all three
/// sessions from their stores and pushes them to their trackers.
@@ -0,0 +1,174 @@
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import '../../i18n/strings.g.dart';
import '../../models/plex_library.dart';
import '../../providers/libraries_provider.dart';
import '../../services/settings_service.dart';
import '../../services/trackers/tracker_constants.dart';
import '../../widgets/app_icon.dart';
import '../../widgets/focusable_list_tile.dart';
import '../../widgets/focused_scroll_scaffold.dart';
import '../../widgets/settings_section.dart';
/// Per-provider library whitelist/blacklist screen. Toggling a switch
/// adds/removes the library from the filter set; "selected" means "in the
/// filter list" in both modes.
class TrackerLibraryFilterScreen extends StatefulWidget {
final TrackerService service;
const TrackerLibraryFilterScreen({super.key, required this.service});
/// Human-readable summary of the current filter state for [service], used as
/// the subtitle on the parent settings screen's "Library filter" tile.
static String subtitleFor(SettingsService settings, TrackerService service) {
final mode = settings.getTrackerLibraryFilterMode(service);
final ids = settings.getTrackerLibraryFilterIds(service);
if (ids.isEmpty) {
return mode == TrackerLibraryFilterMode.blacklist
? t.trackers.libraryFilter.subtitleAllSyncing
: t.trackers.libraryFilter.subtitleNoneSyncing;
}
final count = ids.length.toString();
return mode == TrackerLibraryFilterMode.blacklist
? t.trackers.libraryFilter.subtitleBlocked(count: count)
: t.trackers.libraryFilter.subtitleAllowed(count: count);
}
@override
State<TrackerLibraryFilterScreen> createState() => _TrackerLibraryFilterScreenState();
}
class _TrackerLibraryFilterScreenState extends State<TrackerLibraryFilterScreen> {
late SettingsService _settings;
TrackerLibraryFilterMode _mode = TrackerLibraryFilterMode.blacklist;
final Set<String> _selectedIds = <String>{};
bool _loaded = false;
@override
void initState() {
super.initState();
_load();
}
Future<void> _load() async {
final s = await SettingsService.getInstance();
if (!mounted) return;
setState(() {
_settings = s;
_mode = s.getTrackerLibraryFilterMode(widget.service);
_selectedIds
..clear()
..addAll(s.getTrackerLibraryFilterIds(widget.service));
_loaded = true;
});
}
Future<void> _setMode(TrackerLibraryFilterMode mode) async {
if (mode == _mode) return;
setState(() => _mode = mode);
await _settings.setTrackerLibraryFilterMode(widget.service, mode);
}
Future<void> _toggleLibrary(String globalKey, bool value) async {
setState(() {
if (value) {
_selectedIds.add(globalKey);
} else {
_selectedIds.remove(globalKey);
}
});
await _settings.setTrackerLibraryFilterIds(widget.service, _selectedIds);
}
@override
Widget build(BuildContext context) {
final title = Text(t.trackers.libraryFilter.title);
if (!_loaded) {
return FocusedScrollScaffold(
title: title,
slivers: const [SliverFillRemaining(child: Center(child: CircularProgressIndicator()))],
);
}
final theme = Theme.of(context);
return Consumer<LibrariesProvider>(
builder: (context, provider, _) {
final libraries = provider.libraries;
final grouped = _groupByServer(libraries);
final showServerHeaders = grouped.length > 1;
final children = <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: Text(
_mode == TrackerLibraryFilterMode.blacklist
? t.trackers.libraryFilter.modeHintBlacklist
: t.trackers.libraryFilter.modeHintWhitelist,
style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.onSurfaceVariant),
),
),
SegmentedSetting<TrackerLibraryFilterMode>(
icon: Symbols.filter_list_rounded,
title: t.trackers.libraryFilter.mode,
segments: [
ButtonSegment(
value: TrackerLibraryFilterMode.blacklist,
label: Text(t.trackers.libraryFilter.modeBlacklist),
),
ButtonSegment(
value: TrackerLibraryFilterMode.whitelist,
label: Text(t.trackers.libraryFilter.modeWhitelist),
),
],
selected: _mode,
onChanged: _setMode,
),
SettingsSectionHeader(t.trackers.libraryFilter.libraries),
];
if (libraries.isEmpty) {
children.add(ListTile(title: Text(t.trackers.libraryFilter.noLibraries)));
} else {
for (final entry in grouped.entries) {
if (showServerHeaders) {
children.add(SettingsSectionHeader(entry.value.first.serverName ?? entry.key));
}
for (final lib in entry.value) {
children.add(
FocusableSwitchListTile(
key: ValueKey('tracker-library-filter-${lib.globalKey}'),
secondary: const AppIcon(Symbols.folder_rounded, fill: 1),
title: Text(lib.title),
value: _selectedIds.contains(lib.globalKey),
onChanged: (v) => _toggleLibrary(lib.globalKey, v),
),
);
}
}
}
children.add(const SizedBox(height: 24));
return FocusedScrollScaffold(
title: title,
slivers: [
SliverList(delegate: SliverChildListDelegate(children)),
],
);
},
);
}
static Map<String, List<PlexLibrary>> _groupByServer(List<PlexLibrary> libs) {
final out = <String, List<PlexLibrary>>{};
for (final lib in libs) {
final serverId = lib.serverId;
if (serverId == null) continue;
out.putIfAbsent(serverId, () => <PlexLibrary>[]).add(lib);
}
return out;
}
}
@@ -13,6 +13,7 @@ import '../../services/trackers/anilist/anilist_tracker.dart';
import '../../services/trackers/mal/mal_tracker.dart';
import '../../services/trackers/oauth_proxy_client.dart';
import '../../services/trackers/simkl/simkl_tracker.dart';
import '../../services/trackers/tracker_constants.dart';
import '../../services/settings_service.dart';
import '../../utils/app_logger.dart';
import '../../utils/dialogs.dart';
@@ -22,6 +23,7 @@ import '../../widgets/device_code_dialog.dart';
import '../../widgets/focused_scroll_scaffold.dart';
import '../../widgets/oauth_proxy_dialog.dart';
import '../../widgets/settings_section.dart';
import 'tracker_library_filter_screen.dart';
/// Shared dialog-driven connect flow used by all three trackers. Handles the
/// guard, auto-launch-on-pointer, dialog lifecycle, and failure snackbar —
@@ -114,6 +116,7 @@ Future<void> startSimklConnection(BuildContext context) {
/// Per-service wiring for [TrackerSettingsScreen]. Keeps tracker-specific
/// method names out of the shared screen body.
class TrackerConfig {
final TrackerService service;
final String displayName;
final bool Function(TrackersProvider) isConnected;
final String? Function(TrackersProvider) username;
@@ -122,6 +125,7 @@ class TrackerConfig {
final Future<void> Function(TrackersProvider) disconnect;
const TrackerConfig({
required this.service,
required this.displayName,
required this.isConnected,
required this.username,
@@ -131,6 +135,7 @@ class TrackerConfig {
});
static TrackerConfig mal() => TrackerConfig(
service: TrackerService.mal,
displayName: t.trackers.services.mal,
isConnected: (a) => a.isMalConnected,
username: (a) => a.malUsername,
@@ -143,6 +148,7 @@ class TrackerConfig {
);
static TrackerConfig anilist() => TrackerConfig(
service: TrackerService.anilist,
displayName: t.trackers.services.anilist,
isConnected: (a) => a.isAnilistConnected,
username: (a) => a.anilistUsername,
@@ -155,6 +161,7 @@ class TrackerConfig {
);
static TrackerConfig simkl() => TrackerConfig(
service: TrackerService.simkl,
displayName: t.trackers.services.simkl,
isConnected: (a) => a.isSimklConnected,
username: (a) => a.simklUsername,
@@ -258,6 +265,20 @@ class _TrackerSettingsScreenState extends State<TrackerSettingsScreen> {
await widget.config.setScrobbleEnabled(_settings!, value);
},
),
ListTile(
leading: const AppIcon(Symbols.filter_list_rounded, fill: 1),
title: Text(t.trackers.libraryFilter.title),
subtitle: Text(TrackerLibraryFilterScreen.subtitleFor(_settings!, widget.config.service)),
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
onTap: () async {
await Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => TrackerLibraryFilterScreen(service: widget.config.service),
),
);
if (mounted) setState(() {});
},
),
const Divider(height: 32),
ListTile(
leading: AppIcon(Symbols.link_off_rounded, fill: 1, color: Theme.of(context).colorScheme.error),
@@ -9,6 +9,7 @@ import '../../focus/input_mode_tracker.dart';
import '../../i18n/strings.g.dart';
import '../../providers/trakt_account_provider.dart';
import '../../services/settings_service.dart';
import '../../services/trackers/tracker_constants.dart';
import '../../services/trakt/trakt_scrobble_service.dart';
import '../../services/trakt/trakt_sync_service.dart';
import '../../utils/app_logger.dart';
@@ -18,6 +19,7 @@ import '../../widgets/app_icon.dart';
import '../../widgets/device_code_dialog.dart';
import '../../widgets/focused_scroll_scaffold.dart';
import '../../widgets/settings_section.dart';
import 'tracker_library_filter_screen.dart';
/// Start the Trakt device-code OAuth flow from anywhere in the app.
///
@@ -174,6 +176,20 @@ class _TraktSettingsScreenState extends State<TraktSettingsScreen> {
await TraktSyncService.instance.setEnabled(value);
},
),
ListTile(
leading: const AppIcon(Symbols.filter_list_rounded, fill: 1),
title: Text(t.trackers.libraryFilter.title),
subtitle: Text(TrackerLibraryFilterScreen.subtitleFor(_settings!, TrackerService.trakt)),
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
onTap: () async {
await Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const TrackerLibraryFilterScreen(service: TrackerService.trakt),
),
);
if (mounted) setState(() {});
},
),
const Divider(height: 32),
ListTile(
leading: AppIcon(Symbols.link_off_rounded, fill: 1, color: Theme.of(context).colorScheme.error),
+35
View File
@@ -10,6 +10,7 @@ import '../models/mpv_config_models.dart';
import '../models/external_player_models.dart';
import 'base_shared_preferences_service.dart';
import '../utils/platform_detector.dart';
import 'trackers/tracker_constants.dart';
enum ThemeMode { system, light, dark, oled }
@@ -98,6 +99,8 @@ class SettingsService extends BaseSharedPreferencesService {
static const String _keyEnableMalScrobble = 'enable_mal_scrobble';
static const String _keyEnableAnilistScrobble = 'enable_anilist_scrobble';
static const String _keyEnableSimklScrobble = 'enable_simkl_scrobble';
static const String _prefixTrackerLibraryFilterMode = 'tracker_library_filter_mode_';
static const String _prefixTrackerLibraryFilterIds = 'tracker_library_filter_ids_';
static const String _keyEnableCompanionRemoteServer = 'enable_companion_remote_server';
static const String _keyAutoPip = 'auto_pip';
static const String _keyMatchContentFrameRate = 'match_content_frame_rate';
@@ -1194,6 +1197,34 @@ class SettingsService extends BaseSharedPreferencesService {
return prefs.getBool(_keyEnableSimklScrobble) ?? true;
}
// Per-tracker library filter. Default blacklist+empty = sync every library.
TrackerLibraryFilterMode getTrackerLibraryFilterMode(TrackerService service) => _getEnumValue(
'$_prefixTrackerLibraryFilterMode${service.name}',
TrackerLibraryFilterMode.values,
TrackerLibraryFilterMode.blacklist,
);
Future<void> setTrackerLibraryFilterMode(TrackerService service, TrackerLibraryFilterMode mode) async {
await prefs.setString('$_prefixTrackerLibraryFilterMode${service.name}', mode.name);
}
Set<String> getTrackerLibraryFilterIds(TrackerService service) {
final ids = prefs.getStringList('$_prefixTrackerLibraryFilterIds${service.name}');
return ids?.toSet() ?? <String>{};
}
Future<void> setTrackerLibraryFilterIds(TrackerService service, Set<String> ids) async {
await prefs.setStringList('$_prefixTrackerLibraryFilterIds${service.name}', ids.toList());
}
/// Whether a scrobble/sync for [libraryGlobalKey] should be sent to [service].
/// Null keys bypass the filter so we err on the side of syncing.
bool isLibraryAllowedForTracker(TrackerService service, String? libraryGlobalKey) {
if (libraryGlobalKey == null) return true;
final inList = getTrackerLibraryFilterIds(service).contains(libraryGlobalKey);
return getTrackerLibraryFilterMode(service) == TrackerLibraryFilterMode.blacklist ? !inList : inList;
}
// Companion Remote Server
Future<void> setEnableCompanionRemoteServer(bool enabled) async {
await prefs.setBool(_keyEnableCompanionRemoteServer, enabled);
@@ -1536,6 +1567,10 @@ class SettingsService extends BaseSharedPreferencesService {
prefs.remove(_keyEnableMalScrobble),
prefs.remove(_keyEnableAnilistScrobble),
prefs.remove(_keyEnableSimklScrobble),
...TrackerService.values.expand((s) => [
prefs.remove('$_prefixTrackerLibraryFilterMode${s.name}'),
prefs.remove('$_prefixTrackerLibraryFilterIds${s.name}'),
]),
prefs.remove(_keyAutoPip),
prefs.remove(_keyMatchContentFrameRate),
prefs.remove(_keyTunneledPlayback),
@@ -2,6 +2,7 @@ import '../../../models/trackers/tracker_context.dart';
import '../../../utils/app_logger.dart';
import '../../settings_service.dart';
import '../tracker.dart';
import '../tracker_constants.dart';
import 'anilist_client.dart';
import 'anilist_session.dart';
@@ -17,6 +18,9 @@ class AnilistTracker extends TrackerBase {
@override
String get name => 'anilist';
@override
TrackerService get service => TrackerService.anilist;
@override
bool get needsFribb => true;
@@ -2,6 +2,7 @@ import '../../../models/trackers/tracker_context.dart';
import '../../../utils/app_logger.dart';
import '../../settings_service.dart';
import '../tracker.dart';
import '../tracker_constants.dart';
import 'mal_client.dart';
import 'mal_session.dart';
@@ -22,6 +23,9 @@ class MalTracker extends TrackerBase {
@override
String get name => 'mal';
@override
TrackerService get service => TrackerService.mal;
@override
bool get needsFribb => true;
@@ -2,6 +2,7 @@ import '../../../models/trackers/tracker_context.dart';
import '../../../utils/app_logger.dart';
import '../../settings_service.dart';
import '../tracker.dart';
import '../tracker_constants.dart';
import 'simkl_client.dart';
import 'simkl_session.dart';
@@ -20,6 +21,9 @@ class SimklTracker extends TrackerBase {
@override
String get name => 'simkl';
@override
TrackerService get service => TrackerService.simkl;
@override
bool get needsFribb => false;
+15
View File
@@ -1,5 +1,6 @@
import '../../models/trackers/tracker_context.dart';
import '../settings_service.dart';
import 'tracker_constants.dart';
/// Abstract tracker contract: the coordinator calls [markWatched] once per
/// playback when progress crosses the watched threshold. Enabled/auth gating
@@ -7,6 +8,10 @@ import '../settings_service.dart';
abstract class Tracker {
String get name;
/// Stable identifier used to persist per-service settings (library filter,
/// scrobble enabled, etc.).
TrackerService get service;
bool get canScrobble;
/// True if this tracker's IDs only come from the Fribb anime mapping
@@ -18,6 +23,12 @@ abstract class Tracker {
Future<void> initialize();
Future<void> setEnabled(bool enabled);
/// Whether an item in the given library should be scrobbled. Applies the
/// per-tracker whitelist/blacklist — callers pass the Plex library
/// `serverId:sectionId` globalKey (null when unknown, in which case the
/// filter is bypassed so we err on the side of syncing).
bool shouldScrobbleForLibrary(String? libraryGlobalKey);
Future<void> markWatched(TrackerContext ctx);
}
@@ -45,4 +56,8 @@ abstract class TrackerBase implements Tracker {
Future<void> setEnabled(bool enabled) async {
_isEnabled = enabled;
}
@override
bool shouldScrobbleForLibrary(String? libraryGlobalKey) =>
SettingsService.instanceOrNull?.isLibraryAllowedForTracker(service, libraryGlobalKey) ?? true;
}
@@ -6,3 +6,11 @@ class TrackerConstants {
/// pushed to each tracker.
static const double watchedThresholdPercent = 80.0;
}
/// Identifier used across the app to disambiguate per-service operations.
/// The enum's `.name` forms part of the persistence key — do not rename
/// without a migration.
enum TrackerService { mal, anilist, simkl, trakt }
/// Blacklist+[] syncs every library (the default); whitelist+[] syncs nothing.
enum TrackerLibraryFilterMode { blacklist, whitelist }
@@ -113,7 +113,9 @@ class TrackerCoordinator {
}
Future<void> _dispatchMarkWatched(TrackerContext ctx) async {
final active = _trackers.where((t) => t.canScrobble);
final active = _trackers.where(
(t) => t.canScrobble && t.shouldScrobbleForLibrary(ctx.libraryGlobalKey),
);
await Future.wait(active.map((t) async {
try {
await t.markWatched(ctx);
@@ -127,6 +129,8 @@ class TrackerCoordinator {
final resolver = _resolver;
if (resolver == null) return null;
final libraryKey = metadata.librarySectionGlobalKey;
if (metadata.mediaType == PlexMediaType.movie) {
final ids = await resolver.resolveForMovie(metadata.ratingKey);
if (ids == null) return null;
@@ -134,6 +138,7 @@ class TrackerCoordinator {
external: ids.external,
anime: ids.anime,
ratingKey: metadata.ratingKey,
libraryGlobalKey: libraryKey,
);
}
@@ -147,6 +152,7 @@ class TrackerCoordinator {
external: ids.external,
anime: ids.anime,
ratingKey: metadata.ratingKey,
libraryGlobalKey: libraryKey,
season: season,
episodeNumber: number,
);
@@ -6,6 +6,7 @@ import '../../models/trakt/trakt_scrobble_request.dart';
import '../../utils/app_logger.dart';
import '../plex_client.dart';
import '../settings_service.dart';
import '../trackers/tracker_constants.dart';
import '../trackers/tracker_id_resolver.dart';
import 'trakt_client.dart';
import 'trakt_constants.dart';
@@ -91,6 +92,13 @@ class TraktScrobbleService {
final type = metadata.mediaType;
if (type != PlexMediaType.movie && type != PlexMediaType.episode) return;
final settings = SettingsService.instanceOrNull;
if (settings != null &&
!settings.isLibraryAllowedForTracker(TrackerService.trakt, metadata.librarySectionGlobalKey)) {
appLogger.d('Trakt: library filtered out for ${metadata.ratingKey}');
return;
}
// Seed with the resume offset so the first real position update doesn't
// look like a seek when resuming mid-item.
_currentPosition = metadata.viewOffset != null ? Duration(milliseconds: metadata.viewOffset!) : Duration.zero;
@@ -7,6 +7,7 @@ import '../../utils/app_logger.dart';
import '../../utils/watch_state_notifier.dart';
import '../multi_server_manager.dart';
import '../settings_service.dart';
import '../trackers/tracker_constants.dart';
import '../trackers/tracker_id_resolver.dart';
import 'trakt_client.dart';
import 'trakt_constants.dart';
@@ -109,6 +110,13 @@ class TraktSyncService {
final kind = TraktMediaKind.tryFromPlexType(event.mediaType);
if (kind == null) return;
final settings = SettingsService.instanceOrNull;
if (settings != null &&
!settings.isLibraryAllowedForTracker(TrackerService.trakt, event.librarySectionGlobalKey)) {
appLogger.d('Trakt sync: library filtered out for ${event.ratingKey}');
return;
}
final op = event.changeType == WatchStateChangeType.watched ? TraktSyncOp.add : TraktSyncOp.remove;
await _push(
op: op,
+12
View File
@@ -40,6 +40,10 @@ class WatchStateEvent with HierarchicalEventMixin {
/// Whether item is now considered watched (>90% progress or marked)
final bool? isNowWatched;
/// Library section this item belongs to — used for per-tracker library
/// filtering. Null when emitted without full metadata.
final int? librarySectionID;
WatchStateEvent({
required this.ratingKey,
required this.serverId,
@@ -48,8 +52,14 @@ class WatchStateEvent with HierarchicalEventMixin {
required this.mediaType,
this.viewOffset,
this.isNowWatched,
this.librarySectionID,
}) : globalKey = buildGlobalKey(serverId, ratingKey);
/// `serverId:librarySectionID`, matching [PlexLibrary.globalKey]. Null when
/// the library section is unknown.
String? get librarySectionGlobalKey =>
librarySectionID != null ? buildGlobalKey(serverId, librarySectionID!.toString()) : null;
@override
String toString() => 'WatchStateEvent($changeType, $globalKey, parents: $parentChain)';
}
@@ -88,6 +98,7 @@ class WatchStateNotifier extends BaseNotifier<WatchStateEvent> {
parentChain: metadata.parentChain,
mediaType: metadata.type ?? '',
isNowWatched: isNowWatched,
librarySectionID: metadata.librarySectionID,
),
);
}
@@ -112,6 +123,7 @@ class WatchStateNotifier extends BaseNotifier<WatchStateEvent> {
mediaType: metadata.type ?? '',
viewOffset: viewOffset,
isNowWatched: isNowWatched,
librarySectionID: metadata.librarySectionID,
),
);
}