diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4ade37b4..7e36b06a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -563,5 +563,7 @@ "select_label_color": "Select {label} Color", "default_user_agent": "Defaul user agent", "forceLandscapeMode": "Force landscape mode", - "forceLandscapeModeSubtitle": "Force the player to use landscape orientation." + "forceLandscapeModeSubtitle": "Force the player to use landscape orientation.", + "dns_over_https": "DNS-over-HTTPS (DoH)", + "dns_provider": "DNS Provider" } diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 72bb7178..5b01def0 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -3454,6 +3454,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Force the player to use landscape orientation.'** String get forceLandscapeModeSubtitle; + + /// No description provided for @dns_over_https. + /// + /// In en, this message translates to: + /// **'DNS-over-HTTPS (DoH)'** + String get dns_over_https; + + /// No description provided for @dns_provider. + /// + /// In en, this message translates to: + /// **'DNS Provider'** + String get dns_provider; } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_ar.dart b/lib/l10n/generated/app_localizations_ar.dart index 4cb18034..e4007989 100644 --- a/lib/l10n/generated/app_localizations_ar.dart +++ b/lib/l10n/generated/app_localizations_ar.dart @@ -1788,4 +1788,10 @@ class AppLocalizationsAr extends AppLocalizations { @override String get forceLandscapeModeSubtitle => 'Force the player to use landscape orientation.'; + + @override + String get dns_over_https => 'DNS-over-HTTPS (DoH)'; + + @override + String get dns_provider => 'DNS Provider'; } diff --git a/lib/l10n/generated/app_localizations_as.dart b/lib/l10n/generated/app_localizations_as.dart index cc0fa0aa..9cb64c45 100644 --- a/lib/l10n/generated/app_localizations_as.dart +++ b/lib/l10n/generated/app_localizations_as.dart @@ -1794,4 +1794,10 @@ class AppLocalizationsAs extends AppLocalizations { @override String get forceLandscapeModeSubtitle => 'Force the player to use landscape orientation.'; + + @override + String get dns_over_https => 'DNS-over-HTTPS (DoH)'; + + @override + String get dns_provider => 'DNS Provider'; } diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 26773a9b..5752f9fc 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -1809,4 +1809,10 @@ class AppLocalizationsDe extends AppLocalizations { @override String get forceLandscapeModeSubtitle => 'Force the player to use landscape orientation.'; + + @override + String get dns_over_https => 'DNS-over-HTTPS (DoH)'; + + @override + String get dns_provider => 'DNS Provider'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index df048da5..8444152f 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -1788,4 +1788,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get forceLandscapeModeSubtitle => 'Force the player to use landscape orientation.'; + + @override + String get dns_over_https => 'DNS-over-HTTPS (DoH)'; + + @override + String get dns_provider => 'DNS Provider'; } diff --git a/lib/l10n/generated/app_localizations_es.dart b/lib/l10n/generated/app_localizations_es.dart index 84ae2828..0121cf44 100644 --- a/lib/l10n/generated/app_localizations_es.dart +++ b/lib/l10n/generated/app_localizations_es.dart @@ -1817,6 +1817,12 @@ class AppLocalizationsEs extends AppLocalizations { @override String get forceLandscapeModeSubtitle => 'Force the player to use landscape orientation.'; + + @override + String get dns_over_https => 'DNS-over-HTTPS (DoH)'; + + @override + String get dns_provider => 'DNS Provider'; } /// The translations for Spanish Castilian, as used in Latin America and the Caribbean (`es_419`). diff --git a/lib/l10n/generated/app_localizations_fr.dart b/lib/l10n/generated/app_localizations_fr.dart index ca4a4fad..b26c2389 100644 --- a/lib/l10n/generated/app_localizations_fr.dart +++ b/lib/l10n/generated/app_localizations_fr.dart @@ -1817,4 +1817,10 @@ class AppLocalizationsFr extends AppLocalizations { @override String get forceLandscapeModeSubtitle => 'Force the player to use landscape orientation.'; + + @override + String get dns_over_https => 'DNS-over-HTTPS (DoH)'; + + @override + String get dns_provider => 'DNS Provider'; } diff --git a/lib/l10n/generated/app_localizations_hi.dart b/lib/l10n/generated/app_localizations_hi.dart index ab270155..5052b17c 100644 --- a/lib/l10n/generated/app_localizations_hi.dart +++ b/lib/l10n/generated/app_localizations_hi.dart @@ -1794,4 +1794,10 @@ class AppLocalizationsHi extends AppLocalizations { @override String get forceLandscapeModeSubtitle => 'Force the player to use landscape orientation.'; + + @override + String get dns_over_https => 'DNS-over-HTTPS (DoH)'; + + @override + String get dns_provider => 'DNS Provider'; } diff --git a/lib/l10n/generated/app_localizations_id.dart b/lib/l10n/generated/app_localizations_id.dart index 2610f3bd..f1d3bd38 100644 --- a/lib/l10n/generated/app_localizations_id.dart +++ b/lib/l10n/generated/app_localizations_id.dart @@ -1800,4 +1800,10 @@ class AppLocalizationsId extends AppLocalizations { @override String get forceLandscapeModeSubtitle => 'Force the player to use landscape orientation.'; + + @override + String get dns_over_https => 'DNS-over-HTTPS (DoH)'; + + @override + String get dns_provider => 'DNS Provider'; } diff --git a/lib/l10n/generated/app_localizations_it.dart b/lib/l10n/generated/app_localizations_it.dart index 2b38e15e..98ce71fd 100644 --- a/lib/l10n/generated/app_localizations_it.dart +++ b/lib/l10n/generated/app_localizations_it.dart @@ -1814,4 +1814,10 @@ class AppLocalizationsIt extends AppLocalizations { @override String get forceLandscapeModeSubtitle => 'Force the player to use landscape orientation.'; + + @override + String get dns_over_https => 'DNS-over-HTTPS (DoH)'; + + @override + String get dns_provider => 'DNS Provider'; } diff --git a/lib/l10n/generated/app_localizations_ja.dart b/lib/l10n/generated/app_localizations_ja.dart index 05191b10..98fa9741 100644 --- a/lib/l10n/generated/app_localizations_ja.dart +++ b/lib/l10n/generated/app_localizations_ja.dart @@ -1765,4 +1765,10 @@ class AppLocalizationsJa extends AppLocalizations { @override String get forceLandscapeModeSubtitle => 'Force the player to use landscape orientation.'; + + @override + String get dns_over_https => 'DNS-over-HTTPS (DoH)'; + + @override + String get dns_provider => 'DNS Provider'; } diff --git a/lib/l10n/generated/app_localizations_pt.dart b/lib/l10n/generated/app_localizations_pt.dart index 9fc46d4e..4e726e72 100644 --- a/lib/l10n/generated/app_localizations_pt.dart +++ b/lib/l10n/generated/app_localizations_pt.dart @@ -1812,6 +1812,12 @@ class AppLocalizationsPt extends AppLocalizations { @override String get forceLandscapeModeSubtitle => 'Force the player to use landscape orientation.'; + + @override + String get dns_over_https => 'DNS-over-HTTPS (DoH)'; + + @override + String get dns_provider => 'DNS Provider'; } /// The translations for Portuguese, as used in Brazil (`pt_BR`). diff --git a/lib/l10n/generated/app_localizations_ru.dart b/lib/l10n/generated/app_localizations_ru.dart index c8787c06..ceac9a2c 100644 --- a/lib/l10n/generated/app_localizations_ru.dart +++ b/lib/l10n/generated/app_localizations_ru.dart @@ -1817,4 +1817,10 @@ class AppLocalizationsRu extends AppLocalizations { @override String get forceLandscapeModeSubtitle => 'Force the player to use landscape orientation.'; + + @override + String get dns_over_https => 'DNS-over-HTTPS (DoH)'; + + @override + String get dns_provider => 'DNS Provider'; } diff --git a/lib/l10n/generated/app_localizations_th.dart b/lib/l10n/generated/app_localizations_th.dart index fb3d643f..c7a5abad 100644 --- a/lib/l10n/generated/app_localizations_th.dart +++ b/lib/l10n/generated/app_localizations_th.dart @@ -1788,4 +1788,10 @@ class AppLocalizationsTh extends AppLocalizations { @override String get forceLandscapeModeSubtitle => 'Force the player to use landscape orientation.'; + + @override + String get dns_over_https => 'DNS-over-HTTPS (DoH)'; + + @override + String get dns_provider => 'DNS Provider'; } diff --git a/lib/l10n/generated/app_localizations_tr.dart b/lib/l10n/generated/app_localizations_tr.dart index 550a334e..5543eecd 100644 --- a/lib/l10n/generated/app_localizations_tr.dart +++ b/lib/l10n/generated/app_localizations_tr.dart @@ -1800,4 +1800,10 @@ class AppLocalizationsTr extends AppLocalizations { @override String get forceLandscapeModeSubtitle => 'Force the player to use landscape orientation.'; + + @override + String get dns_over_https => 'DNS-over-HTTPS (DoH)'; + + @override + String get dns_provider => 'DNS Provider'; } diff --git a/lib/l10n/generated/app_localizations_zh.dart b/lib/l10n/generated/app_localizations_zh.dart index 4b6cc886..b1a1e052 100644 --- a/lib/l10n/generated/app_localizations_zh.dart +++ b/lib/l10n/generated/app_localizations_zh.dart @@ -1745,4 +1745,10 @@ class AppLocalizationsZh extends AppLocalizations { @override String get forceLandscapeModeSubtitle => '强制播放器使用横屏方向。'; + + @override + String get dns_over_https => 'DNS-over-HTTPS (DoH)'; + + @override + String get dns_provider => 'DNS Provider'; } diff --git a/lib/models/settings.dart b/lib/models/settings.dart index bea2dcd1..dc9f9ed6 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -175,6 +175,10 @@ class Settings { String? customDns; + bool? doHEnabled; + + int? doHProviderId; + String? btServerAddress; int? btServerPort; @@ -383,6 +387,8 @@ class Settings { this.enableAutoSkip, this.aniSkipTimeoutLength, this.customDns = "", + this.doHEnabled = false, + this.doHProviderId = 0, this.btServerAddress = "127.0.0.1", this.btServerPort, this.fullScreenReader = true, @@ -590,6 +596,8 @@ class Settings { enableAutoSkip = json['enableAutoSkip']; aniSkipTimeoutLength = json['aniSkipTimeoutLength']; customDns = json['customDns']; + doHEnabled = json['doHEnabled']; + doHProviderId = json['doHProviderId']; btServerAddress = json['btServerAddress']; btServerPort = json['btServerPort']; customColorFilter = json['customColorFilter'] != null @@ -797,6 +805,8 @@ class Settings { 'enableAutoSkip': enableAutoSkip, 'aniSkipTimeoutLength': aniSkipTimeoutLength, 'customDns': customDns, + 'doHEnabled': doHEnabled, + 'doHProviderId': doHProviderId, 'btServerAddress': btServerAddress, 'btServerPort': btServerPort, 'fullScreenReader': fullScreenReader, diff --git a/lib/models/settings.g.dart b/lib/models/settings.g.dart index 7e33216a..0e60b109 100644 --- a/lib/models/settings.g.dart +++ b/lib/models/settings.g.dart @@ -282,504 +282,514 @@ const SettingsSchema = CollectionSchema( type: IsarType.byte, enumMap: _SettingsdisplayTypeEnumValueMap, ), - r'doubleTapAnimationSpeed': PropertySchema( + r'doHEnabled': PropertySchema( id: 47, + name: r'doHEnabled', + type: IsarType.bool, + ), + r'doHProviderId': PropertySchema( + id: 48, + name: r'doHProviderId', + type: IsarType.long, + ), + r'doubleTapAnimationSpeed': PropertySchema( + id: 49, name: r'doubleTapAnimationSpeed', type: IsarType.long, ), r'downloadLocation': PropertySchema( - id: 48, + id: 50, name: r'downloadLocation', type: IsarType.string, ), r'downloadOnlyOnWifi': PropertySchema( - id: 49, + id: 51, name: r'downloadOnlyOnWifi', type: IsarType.bool, ), r'downloadedOnlyMode': PropertySchema( - id: 50, + id: 52, name: r'downloadedOnlyMode', type: IsarType.bool, ), r'enableAniSkip': PropertySchema( - id: 51, + id: 53, name: r'enableAniSkip', type: IsarType.bool, ), r'enableAudioPitchCorrection': PropertySchema( - id: 52, + id: 54, name: r'enableAudioPitchCorrection', type: IsarType.bool, ), r'enableAutoSkip': PropertySchema( - id: 53, + id: 55, name: r'enableAutoSkip', type: IsarType.bool, ), r'enableCustomColorFilter': PropertySchema( - id: 54, + id: 56, name: r'enableCustomColorFilter', type: IsarType.bool, ), r'enableDiscordRpc': PropertySchema( - id: 55, + id: 57, name: r'enableDiscordRpc', type: IsarType.bool, ), r'enableGpuNext': PropertySchema( - id: 56, + id: 58, name: r'enableGpuNext', type: IsarType.bool, ), r'enableHardwareAcceleration': PropertySchema( - id: 57, + id: 59, name: r'enableHardwareAcceleration', type: IsarType.bool, ), r'enableLogs': PropertySchema( - id: 58, + id: 60, name: r'enableLogs', type: IsarType.bool, ), r'filterScanlatorList': PropertySchema( - id: 59, + id: 61, name: r'filterScanlatorList', type: IsarType.objectList, target: r'FilterScanlator', ), r'flexColorSchemeBlendLevel': PropertySchema( - id: 60, + id: 62, name: r'flexColorSchemeBlendLevel', type: IsarType.double, ), r'flexSchemeColorIndex': PropertySchema( - id: 61, + id: 63, name: r'flexSchemeColorIndex', type: IsarType.long, ), r'followSystemTheme': PropertySchema( - id: 62, + id: 64, name: r'followSystemTheme', type: IsarType.bool, ), r'forceLandscapePlayer': PropertySchema( - id: 63, + id: 65, name: r'forceLandscapePlayer', type: IsarType.bool, ), r'fullScreenPlayer': PropertySchema( - id: 64, + id: 66, name: r'fullScreenPlayer', type: IsarType.bool, ), r'fullScreenReader': PropertySchema( - id: 65, + id: 67, name: r'fullScreenReader', type: IsarType.bool, ), r'hideDiscordRpcInIncognito': PropertySchema( - id: 66, + id: 68, name: r'hideDiscordRpcInIncognito', type: IsarType.bool, ), r'hideItems': PropertySchema( - id: 67, + id: 69, name: r'hideItems', type: IsarType.stringList, ), r'hwdecMode': PropertySchema( - id: 68, + id: 70, name: r'hwdecMode', type: IsarType.string, ), r'incognitoMode': PropertySchema( - id: 69, + id: 71, name: r'incognitoMode', type: IsarType.bool, ), r'lastTrackerLibraryLocation': PropertySchema( - id: 70, + id: 72, name: r'lastTrackerLibraryLocation', type: IsarType.string, ), r'libraryDownloadedChapters': PropertySchema( - id: 71, + id: 73, name: r'libraryDownloadedChapters', type: IsarType.bool, ), r'libraryFilterAnimeBookMarkedType': PropertySchema( - id: 72, + id: 74, name: r'libraryFilterAnimeBookMarkedType', type: IsarType.long, ), r'libraryFilterAnimeDownloadType': PropertySchema( - id: 73, + id: 75, name: r'libraryFilterAnimeDownloadType', type: IsarType.long, ), r'libraryFilterAnimeStartedType': PropertySchema( - id: 74, + id: 76, name: r'libraryFilterAnimeStartedType', type: IsarType.long, ), r'libraryFilterAnimeUnreadType': PropertySchema( - id: 75, + id: 77, name: r'libraryFilterAnimeUnreadType', type: IsarType.long, ), r'libraryFilterMangasBookMarkedType': PropertySchema( - id: 76, + id: 78, name: r'libraryFilterMangasBookMarkedType', type: IsarType.long, ), r'libraryFilterMangasDownloadType': PropertySchema( - id: 77, + id: 79, name: r'libraryFilterMangasDownloadType', type: IsarType.long, ), r'libraryFilterMangasStartedType': PropertySchema( - id: 78, + id: 80, name: r'libraryFilterMangasStartedType', type: IsarType.long, ), r'libraryFilterMangasUnreadType': PropertySchema( - id: 79, + id: 81, name: r'libraryFilterMangasUnreadType', type: IsarType.long, ), r'libraryFilterNovelBookMarkedType': PropertySchema( - id: 80, + id: 82, name: r'libraryFilterNovelBookMarkedType', type: IsarType.long, ), r'libraryFilterNovelDownloadType': PropertySchema( - id: 81, + id: 83, name: r'libraryFilterNovelDownloadType', type: IsarType.long, ), r'libraryFilterNovelStartedType': PropertySchema( - id: 82, + id: 84, name: r'libraryFilterNovelStartedType', type: IsarType.long, ), r'libraryFilterNovelUnreadType': PropertySchema( - id: 83, + id: 85, name: r'libraryFilterNovelUnreadType', type: IsarType.long, ), r'libraryLocalSource': PropertySchema( - id: 84, + id: 86, name: r'libraryLocalSource', type: IsarType.bool, ), r'libraryShowCategoryTabs': PropertySchema( - id: 85, + id: 87, name: r'libraryShowCategoryTabs', type: IsarType.bool, ), r'libraryShowContinueReadingButton': PropertySchema( - id: 86, + id: 88, name: r'libraryShowContinueReadingButton', type: IsarType.bool, ), r'libraryShowLanguage': PropertySchema( - id: 87, + id: 89, name: r'libraryShowLanguage', type: IsarType.bool, ), r'libraryShowNumbersOfItems': PropertySchema( - id: 88, + id: 90, name: r'libraryShowNumbersOfItems', type: IsarType.bool, ), r'localFolders': PropertySchema( - id: 89, + id: 91, name: r'localFolders', type: IsarType.stringList, ), r'locale': PropertySchema( - id: 90, + id: 92, name: r'locale', type: IsarType.object, target: r'L10nLocale', ), r'mangaExtensionsRepo': PropertySchema( - id: 91, + id: 93, name: r'mangaExtensionsRepo', type: IsarType.objectList, target: r'Repo', ), r'mangaGridSize': PropertySchema( - id: 92, + id: 94, name: r'mangaGridSize', type: IsarType.long, ), r'mangaHomeDisplayType': PropertySchema( - id: 93, + id: 95, name: r'mangaHomeDisplayType', type: IsarType.byte, enumMap: _SettingsmangaHomeDisplayTypeEnumValueMap, ), r'markEpisodeAsSeenType': PropertySchema( - id: 94, + id: 96, name: r'markEpisodeAsSeenType', type: IsarType.long, ), r'mergeLibraryNavMobile': PropertySchema( - id: 95, + id: 97, name: r'mergeLibraryNavMobile', type: IsarType.bool, ), r'navigationOrder': PropertySchema( - id: 96, + id: 98, name: r'navigationOrder', type: IsarType.stringList, ), r'novelDisplayType': PropertySchema( - id: 97, + id: 99, name: r'novelDisplayType', type: IsarType.byte, enumMap: _SettingsnovelDisplayTypeEnumValueMap, ), r'novelExtensionsRepo': PropertySchema( - id: 98, + id: 100, name: r'novelExtensionsRepo', type: IsarType.objectList, target: r'Repo', ), r'novelFontSize': PropertySchema( - id: 99, + id: 101, name: r'novelFontSize', type: IsarType.long, ), r'novelGridSize': PropertySchema( - id: 100, + id: 102, name: r'novelGridSize', type: IsarType.long, ), r'novelLibraryDownloadedChapters': PropertySchema( - id: 101, + id: 103, name: r'novelLibraryDownloadedChapters', type: IsarType.bool, ), r'novelLibraryLocalSource': PropertySchema( - id: 102, + id: 104, name: r'novelLibraryLocalSource', type: IsarType.bool, ), r'novelLibraryShowCategoryTabs': PropertySchema( - id: 103, + id: 105, name: r'novelLibraryShowCategoryTabs', type: IsarType.bool, ), r'novelLibraryShowContinueReadingButton': PropertySchema( - id: 104, + id: 106, name: r'novelLibraryShowContinueReadingButton', type: IsarType.bool, ), r'novelLibraryShowLanguage': PropertySchema( - id: 105, + id: 107, name: r'novelLibraryShowLanguage', type: IsarType.bool, ), r'novelLibraryShowNumbersOfItems': PropertySchema( - id: 106, + id: 108, name: r'novelLibraryShowNumbersOfItems', type: IsarType.bool, ), r'novelReaderLineHeight': PropertySchema( - id: 107, + id: 109, name: r'novelReaderLineHeight', type: IsarType.double, ), r'novelReaderPadding': PropertySchema( - id: 108, + id: 110, name: r'novelReaderPadding', type: IsarType.long, ), r'novelReaderTextColor': PropertySchema( - id: 109, + id: 111, name: r'novelReaderTextColor', type: IsarType.string, ), r'novelReaderTheme': PropertySchema( - id: 110, + id: 112, name: r'novelReaderTheme', type: IsarType.string, ), r'novelRemoveExtraParagraphSpacing': PropertySchema( - id: 111, + id: 113, name: r'novelRemoveExtraParagraphSpacing', type: IsarType.bool, ), r'novelShowScrollPercentage': PropertySchema( - id: 112, + id: 114, name: r'novelShowScrollPercentage', type: IsarType.bool, ), r'novelTapToScroll': PropertySchema( - id: 113, + id: 115, name: r'novelTapToScroll', type: IsarType.bool, ), r'novelTextAlign': PropertySchema( - id: 114, + id: 116, name: r'novelTextAlign', type: IsarType.byte, enumMap: _SettingsnovelTextAlignEnumValueMap, ), r'onlyIncludePinnedSources': PropertySchema( - id: 115, + id: 117, name: r'onlyIncludePinnedSources', type: IsarType.bool, ), r'pagePreloadAmount': PropertySchema( - id: 116, + id: 118, name: r'pagePreloadAmount', type: IsarType.long, ), r'personalPageModeList': PropertySchema( - id: 117, + id: 119, name: r'personalPageModeList', type: IsarType.objectList, target: r'PersonalPageMode', ), r'personalReaderModeList': PropertySchema( - id: 118, + id: 120, name: r'personalReaderModeList', type: IsarType.objectList, target: r'PersonalReaderMode', ), r'playerSubtitleSettings': PropertySchema( - id: 119, + id: 121, name: r'playerSubtitleSettings', type: IsarType.object, target: r'PlayerSubtitleSettings', ), r'pureBlackDarkMode': PropertySchema( - id: 120, + id: 122, name: r'pureBlackDarkMode', type: IsarType.bool, ), r'relativeTimesTamps': PropertySchema( - id: 121, + id: 123, name: r'relativeTimesTamps', type: IsarType.long, ), r'rpcShowCoverImage': PropertySchema( - id: 122, + id: 124, name: r'rpcShowCoverImage', type: IsarType.bool, ), r'rpcShowReadingWatchingProgress': PropertySchema( - id: 123, + id: 125, name: r'rpcShowReadingWatchingProgress', type: IsarType.bool, ), r'rpcShowTitle': PropertySchema( - id: 124, + id: 126, name: r'rpcShowTitle', type: IsarType.bool, ), r'saveAsCBZArchive': PropertySchema( - id: 125, + id: 127, name: r'saveAsCBZArchive', type: IsarType.bool, ), r'scaleType': PropertySchema( - id: 126, + id: 128, name: r'scaleType', type: IsarType.byte, enumMap: _SettingsscaleTypeEnumValueMap, ), r'showPagesNumber': PropertySchema( - id: 127, + id: 129, name: r'showPagesNumber', type: IsarType.bool, ), r'sortChapterList': PropertySchema( - id: 128, + id: 130, name: r'sortChapterList', type: IsarType.objectList, target: r'SortChapter', ), r'sortLibraryAnime': PropertySchema( - id: 129, + id: 131, name: r'sortLibraryAnime', type: IsarType.object, target: r'SortLibraryManga', ), r'sortLibraryManga': PropertySchema( - id: 130, + id: 132, name: r'sortLibraryManga', type: IsarType.object, target: r'SortLibraryManga', ), r'sortLibraryNovel': PropertySchema( - id: 131, + id: 133, name: r'sortLibraryNovel', type: IsarType.object, target: r'SortLibraryManga', ), r'startDatebackup': PropertySchema( - id: 132, + id: 134, name: r'startDatebackup', type: IsarType.long, ), r'themeIsDark': PropertySchema( - id: 133, + id: 135, name: r'themeIsDark', type: IsarType.bool, ), r'updateProgressAfterReading': PropertySchema( - id: 134, + id: 136, name: r'updateProgressAfterReading', type: IsarType.bool, ), r'updatedAt': PropertySchema( - id: 135, + id: 137, name: r'updatedAt', type: IsarType.long, ), r'useLibass': PropertySchema( - id: 136, + id: 138, name: r'useLibass', type: IsarType.bool, ), r'useMpvConfig': PropertySchema( - id: 137, + id: 139, name: r'useMpvConfig', type: IsarType.bool, ), r'usePageTapZones': PropertySchema( - id: 138, + id: 140, name: r'usePageTapZones', type: IsarType.bool, ), r'useYUV420P': PropertySchema( - id: 139, + id: 141, name: r'useYUV420P', type: IsarType.bool, ), r'userAgent': PropertySchema( - id: 140, + id: 142, name: r'userAgent', type: IsarType.string, ), r'volumeBoostCap': PropertySchema( - id: 141, + id: 143, name: r'volumeBoostCap', type: IsarType.long, ), @@ -1380,156 +1390,158 @@ void _settingsSerialize( ); writer.writeByte(offsets[45], object.disableSectionType.index); writer.writeByte(offsets[46], object.displayType.index); - writer.writeLong(offsets[47], object.doubleTapAnimationSpeed); - writer.writeString(offsets[48], object.downloadLocation); - writer.writeBool(offsets[49], object.downloadOnlyOnWifi); - writer.writeBool(offsets[50], object.downloadedOnlyMode); - writer.writeBool(offsets[51], object.enableAniSkip); - writer.writeBool(offsets[52], object.enableAudioPitchCorrection); - writer.writeBool(offsets[53], object.enableAutoSkip); - writer.writeBool(offsets[54], object.enableCustomColorFilter); - writer.writeBool(offsets[55], object.enableDiscordRpc); - writer.writeBool(offsets[56], object.enableGpuNext); - writer.writeBool(offsets[57], object.enableHardwareAcceleration); - writer.writeBool(offsets[58], object.enableLogs); + writer.writeBool(offsets[47], object.doHEnabled); + writer.writeLong(offsets[48], object.doHProviderId); + writer.writeLong(offsets[49], object.doubleTapAnimationSpeed); + writer.writeString(offsets[50], object.downloadLocation); + writer.writeBool(offsets[51], object.downloadOnlyOnWifi); + writer.writeBool(offsets[52], object.downloadedOnlyMode); + writer.writeBool(offsets[53], object.enableAniSkip); + writer.writeBool(offsets[54], object.enableAudioPitchCorrection); + writer.writeBool(offsets[55], object.enableAutoSkip); + writer.writeBool(offsets[56], object.enableCustomColorFilter); + writer.writeBool(offsets[57], object.enableDiscordRpc); + writer.writeBool(offsets[58], object.enableGpuNext); + writer.writeBool(offsets[59], object.enableHardwareAcceleration); + writer.writeBool(offsets[60], object.enableLogs); writer.writeObjectList( - offsets[59], + offsets[61], allOffsets, FilterScanlatorSchema.serialize, object.filterScanlatorList, ); - writer.writeDouble(offsets[60], object.flexColorSchemeBlendLevel); - writer.writeLong(offsets[61], object.flexSchemeColorIndex); - writer.writeBool(offsets[62], object.followSystemTheme); - writer.writeBool(offsets[63], object.forceLandscapePlayer); - writer.writeBool(offsets[64], object.fullScreenPlayer); - writer.writeBool(offsets[65], object.fullScreenReader); - writer.writeBool(offsets[66], object.hideDiscordRpcInIncognito); - writer.writeStringList(offsets[67], object.hideItems); - writer.writeString(offsets[68], object.hwdecMode); - writer.writeBool(offsets[69], object.incognitoMode); - writer.writeString(offsets[70], object.lastTrackerLibraryLocation); - writer.writeBool(offsets[71], object.libraryDownloadedChapters); - writer.writeLong(offsets[72], object.libraryFilterAnimeBookMarkedType); - writer.writeLong(offsets[73], object.libraryFilterAnimeDownloadType); - writer.writeLong(offsets[74], object.libraryFilterAnimeStartedType); - writer.writeLong(offsets[75], object.libraryFilterAnimeUnreadType); - writer.writeLong(offsets[76], object.libraryFilterMangasBookMarkedType); - writer.writeLong(offsets[77], object.libraryFilterMangasDownloadType); - writer.writeLong(offsets[78], object.libraryFilterMangasStartedType); - writer.writeLong(offsets[79], object.libraryFilterMangasUnreadType); - writer.writeLong(offsets[80], object.libraryFilterNovelBookMarkedType); - writer.writeLong(offsets[81], object.libraryFilterNovelDownloadType); - writer.writeLong(offsets[82], object.libraryFilterNovelStartedType); - writer.writeLong(offsets[83], object.libraryFilterNovelUnreadType); - writer.writeBool(offsets[84], object.libraryLocalSource); - writer.writeBool(offsets[85], object.libraryShowCategoryTabs); - writer.writeBool(offsets[86], object.libraryShowContinueReadingButton); - writer.writeBool(offsets[87], object.libraryShowLanguage); - writer.writeBool(offsets[88], object.libraryShowNumbersOfItems); - writer.writeStringList(offsets[89], object.localFolders); + writer.writeDouble(offsets[62], object.flexColorSchemeBlendLevel); + writer.writeLong(offsets[63], object.flexSchemeColorIndex); + writer.writeBool(offsets[64], object.followSystemTheme); + writer.writeBool(offsets[65], object.forceLandscapePlayer); + writer.writeBool(offsets[66], object.fullScreenPlayer); + writer.writeBool(offsets[67], object.fullScreenReader); + writer.writeBool(offsets[68], object.hideDiscordRpcInIncognito); + writer.writeStringList(offsets[69], object.hideItems); + writer.writeString(offsets[70], object.hwdecMode); + writer.writeBool(offsets[71], object.incognitoMode); + writer.writeString(offsets[72], object.lastTrackerLibraryLocation); + writer.writeBool(offsets[73], object.libraryDownloadedChapters); + writer.writeLong(offsets[74], object.libraryFilterAnimeBookMarkedType); + writer.writeLong(offsets[75], object.libraryFilterAnimeDownloadType); + writer.writeLong(offsets[76], object.libraryFilterAnimeStartedType); + writer.writeLong(offsets[77], object.libraryFilterAnimeUnreadType); + writer.writeLong(offsets[78], object.libraryFilterMangasBookMarkedType); + writer.writeLong(offsets[79], object.libraryFilterMangasDownloadType); + writer.writeLong(offsets[80], object.libraryFilterMangasStartedType); + writer.writeLong(offsets[81], object.libraryFilterMangasUnreadType); + writer.writeLong(offsets[82], object.libraryFilterNovelBookMarkedType); + writer.writeLong(offsets[83], object.libraryFilterNovelDownloadType); + writer.writeLong(offsets[84], object.libraryFilterNovelStartedType); + writer.writeLong(offsets[85], object.libraryFilterNovelUnreadType); + writer.writeBool(offsets[86], object.libraryLocalSource); + writer.writeBool(offsets[87], object.libraryShowCategoryTabs); + writer.writeBool(offsets[88], object.libraryShowContinueReadingButton); + writer.writeBool(offsets[89], object.libraryShowLanguage); + writer.writeBool(offsets[90], object.libraryShowNumbersOfItems); + writer.writeStringList(offsets[91], object.localFolders); writer.writeObject( - offsets[90], + offsets[92], allOffsets, L10nLocaleSchema.serialize, object.locale, ); writer.writeObjectList( - offsets[91], + offsets[93], allOffsets, RepoSchema.serialize, object.mangaExtensionsRepo, ); - writer.writeLong(offsets[92], object.mangaGridSize); - writer.writeByte(offsets[93], object.mangaHomeDisplayType.index); - writer.writeLong(offsets[94], object.markEpisodeAsSeenType); - writer.writeBool(offsets[95], object.mergeLibraryNavMobile); - writer.writeStringList(offsets[96], object.navigationOrder); - writer.writeByte(offsets[97], object.novelDisplayType.index); + writer.writeLong(offsets[94], object.mangaGridSize); + writer.writeByte(offsets[95], object.mangaHomeDisplayType.index); + writer.writeLong(offsets[96], object.markEpisodeAsSeenType); + writer.writeBool(offsets[97], object.mergeLibraryNavMobile); + writer.writeStringList(offsets[98], object.navigationOrder); + writer.writeByte(offsets[99], object.novelDisplayType.index); writer.writeObjectList( - offsets[98], + offsets[100], allOffsets, RepoSchema.serialize, object.novelExtensionsRepo, ); - writer.writeLong(offsets[99], object.novelFontSize); - writer.writeLong(offsets[100], object.novelGridSize); - writer.writeBool(offsets[101], object.novelLibraryDownloadedChapters); - writer.writeBool(offsets[102], object.novelLibraryLocalSource); - writer.writeBool(offsets[103], object.novelLibraryShowCategoryTabs); - writer.writeBool(offsets[104], object.novelLibraryShowContinueReadingButton); - writer.writeBool(offsets[105], object.novelLibraryShowLanguage); - writer.writeBool(offsets[106], object.novelLibraryShowNumbersOfItems); - writer.writeDouble(offsets[107], object.novelReaderLineHeight); - writer.writeLong(offsets[108], object.novelReaderPadding); - writer.writeString(offsets[109], object.novelReaderTextColor); - writer.writeString(offsets[110], object.novelReaderTheme); - writer.writeBool(offsets[111], object.novelRemoveExtraParagraphSpacing); - writer.writeBool(offsets[112], object.novelShowScrollPercentage); - writer.writeBool(offsets[113], object.novelTapToScroll); - writer.writeByte(offsets[114], object.novelTextAlign.index); - writer.writeBool(offsets[115], object.onlyIncludePinnedSources); - writer.writeLong(offsets[116], object.pagePreloadAmount); + writer.writeLong(offsets[101], object.novelFontSize); + writer.writeLong(offsets[102], object.novelGridSize); + writer.writeBool(offsets[103], object.novelLibraryDownloadedChapters); + writer.writeBool(offsets[104], object.novelLibraryLocalSource); + writer.writeBool(offsets[105], object.novelLibraryShowCategoryTabs); + writer.writeBool(offsets[106], object.novelLibraryShowContinueReadingButton); + writer.writeBool(offsets[107], object.novelLibraryShowLanguage); + writer.writeBool(offsets[108], object.novelLibraryShowNumbersOfItems); + writer.writeDouble(offsets[109], object.novelReaderLineHeight); + writer.writeLong(offsets[110], object.novelReaderPadding); + writer.writeString(offsets[111], object.novelReaderTextColor); + writer.writeString(offsets[112], object.novelReaderTheme); + writer.writeBool(offsets[113], object.novelRemoveExtraParagraphSpacing); + writer.writeBool(offsets[114], object.novelShowScrollPercentage); + writer.writeBool(offsets[115], object.novelTapToScroll); + writer.writeByte(offsets[116], object.novelTextAlign.index); + writer.writeBool(offsets[117], object.onlyIncludePinnedSources); + writer.writeLong(offsets[118], object.pagePreloadAmount); writer.writeObjectList( - offsets[117], + offsets[119], allOffsets, PersonalPageModeSchema.serialize, object.personalPageModeList, ); writer.writeObjectList( - offsets[118], + offsets[120], allOffsets, PersonalReaderModeSchema.serialize, object.personalReaderModeList, ); writer.writeObject( - offsets[119], + offsets[121], allOffsets, PlayerSubtitleSettingsSchema.serialize, object.playerSubtitleSettings, ); - writer.writeBool(offsets[120], object.pureBlackDarkMode); - writer.writeLong(offsets[121], object.relativeTimesTamps); - writer.writeBool(offsets[122], object.rpcShowCoverImage); - writer.writeBool(offsets[123], object.rpcShowReadingWatchingProgress); - writer.writeBool(offsets[124], object.rpcShowTitle); - writer.writeBool(offsets[125], object.saveAsCBZArchive); - writer.writeByte(offsets[126], object.scaleType.index); - writer.writeBool(offsets[127], object.showPagesNumber); + writer.writeBool(offsets[122], object.pureBlackDarkMode); + writer.writeLong(offsets[123], object.relativeTimesTamps); + writer.writeBool(offsets[124], object.rpcShowCoverImage); + writer.writeBool(offsets[125], object.rpcShowReadingWatchingProgress); + writer.writeBool(offsets[126], object.rpcShowTitle); + writer.writeBool(offsets[127], object.saveAsCBZArchive); + writer.writeByte(offsets[128], object.scaleType.index); + writer.writeBool(offsets[129], object.showPagesNumber); writer.writeObjectList( - offsets[128], + offsets[130], allOffsets, SortChapterSchema.serialize, object.sortChapterList, ); writer.writeObject( - offsets[129], + offsets[131], allOffsets, SortLibraryMangaSchema.serialize, object.sortLibraryAnime, ); writer.writeObject( - offsets[130], + offsets[132], allOffsets, SortLibraryMangaSchema.serialize, object.sortLibraryManga, ); writer.writeObject( - offsets[131], + offsets[133], allOffsets, SortLibraryMangaSchema.serialize, object.sortLibraryNovel, ); - writer.writeLong(offsets[132], object.startDatebackup); - writer.writeBool(offsets[133], object.themeIsDark); - writer.writeBool(offsets[134], object.updateProgressAfterReading); - writer.writeLong(offsets[135], object.updatedAt); - writer.writeBool(offsets[136], object.useLibass); - writer.writeBool(offsets[137], object.useMpvConfig); - writer.writeBool(offsets[138], object.usePageTapZones); - writer.writeBool(offsets[139], object.useYUV420P); - writer.writeString(offsets[140], object.userAgent); - writer.writeLong(offsets[141], object.volumeBoostCap); + writer.writeLong(offsets[134], object.startDatebackup); + writer.writeBool(offsets[135], object.themeIsDark); + writer.writeBool(offsets[136], object.updateProgressAfterReading); + writer.writeLong(offsets[137], object.updatedAt); + writer.writeBool(offsets[138], object.useLibass); + writer.writeBool(offsets[139], object.useMpvConfig); + writer.writeBool(offsets[140], object.usePageTapZones); + writer.writeBool(offsets[141], object.useYUV420P); + writer.writeString(offsets[142], object.userAgent); + writer.writeLong(offsets[143], object.volumeBoostCap); } Settings _settingsDeserialize( @@ -1651,154 +1663,156 @@ Settings _settingsDeserialize( displayType: _SettingsdisplayTypeValueEnumMap[reader.readByteOrNull(offsets[46])] ?? DisplayType.compactGrid, - doubleTapAnimationSpeed: reader.readLongOrNull(offsets[47]), - downloadLocation: reader.readStringOrNull(offsets[48]), - downloadOnlyOnWifi: reader.readBoolOrNull(offsets[49]), - downloadedOnlyMode: reader.readBoolOrNull(offsets[50]), - enableAniSkip: reader.readBoolOrNull(offsets[51]), - enableAudioPitchCorrection: reader.readBoolOrNull(offsets[52]), - enableAutoSkip: reader.readBoolOrNull(offsets[53]), - enableCustomColorFilter: reader.readBoolOrNull(offsets[54]), - enableDiscordRpc: reader.readBoolOrNull(offsets[55]), - enableGpuNext: reader.readBoolOrNull(offsets[56]), - enableHardwareAcceleration: reader.readBoolOrNull(offsets[57]), - enableLogs: reader.readBoolOrNull(offsets[58]), - flexColorSchemeBlendLevel: reader.readDoubleOrNull(offsets[60]), - flexSchemeColorIndex: reader.readLongOrNull(offsets[61]), - followSystemTheme: reader.readBoolOrNull(offsets[62]), - forceLandscapePlayer: reader.readBoolOrNull(offsets[63]), - fullScreenPlayer: reader.readBoolOrNull(offsets[64]), - fullScreenReader: reader.readBoolOrNull(offsets[65]), - hideDiscordRpcInIncognito: reader.readBoolOrNull(offsets[66]), - hideItems: reader.readStringList(offsets[67]), - hwdecMode: reader.readStringOrNull(offsets[68]), + doHEnabled: reader.readBoolOrNull(offsets[47]), + doHProviderId: reader.readLongOrNull(offsets[48]), + doubleTapAnimationSpeed: reader.readLongOrNull(offsets[49]), + downloadLocation: reader.readStringOrNull(offsets[50]), + downloadOnlyOnWifi: reader.readBoolOrNull(offsets[51]), + downloadedOnlyMode: reader.readBoolOrNull(offsets[52]), + enableAniSkip: reader.readBoolOrNull(offsets[53]), + enableAudioPitchCorrection: reader.readBoolOrNull(offsets[54]), + enableAutoSkip: reader.readBoolOrNull(offsets[55]), + enableCustomColorFilter: reader.readBoolOrNull(offsets[56]), + enableDiscordRpc: reader.readBoolOrNull(offsets[57]), + enableGpuNext: reader.readBoolOrNull(offsets[58]), + enableHardwareAcceleration: reader.readBoolOrNull(offsets[59]), + enableLogs: reader.readBoolOrNull(offsets[60]), + flexColorSchemeBlendLevel: reader.readDoubleOrNull(offsets[62]), + flexSchemeColorIndex: reader.readLongOrNull(offsets[63]), + followSystemTheme: reader.readBoolOrNull(offsets[64]), + forceLandscapePlayer: reader.readBoolOrNull(offsets[65]), + fullScreenPlayer: reader.readBoolOrNull(offsets[66]), + fullScreenReader: reader.readBoolOrNull(offsets[67]), + hideDiscordRpcInIncognito: reader.readBoolOrNull(offsets[68]), + hideItems: reader.readStringList(offsets[69]), + hwdecMode: reader.readStringOrNull(offsets[70]), id: id, - incognitoMode: reader.readBoolOrNull(offsets[69]), - lastTrackerLibraryLocation: reader.readStringOrNull(offsets[70]), - libraryDownloadedChapters: reader.readBoolOrNull(offsets[71]), - libraryFilterAnimeBookMarkedType: reader.readLongOrNull(offsets[72]), - libraryFilterAnimeDownloadType: reader.readLongOrNull(offsets[73]), - libraryFilterAnimeStartedType: reader.readLongOrNull(offsets[74]), - libraryFilterAnimeUnreadType: reader.readLongOrNull(offsets[75]), - libraryFilterMangasBookMarkedType: reader.readLongOrNull(offsets[76]), - libraryFilterMangasDownloadType: reader.readLongOrNull(offsets[77]), - libraryFilterMangasStartedType: reader.readLongOrNull(offsets[78]), - libraryFilterMangasUnreadType: reader.readLongOrNull(offsets[79]), - libraryFilterNovelBookMarkedType: reader.readLongOrNull(offsets[80]), - libraryFilterNovelDownloadType: reader.readLongOrNull(offsets[81]), - libraryFilterNovelStartedType: reader.readLongOrNull(offsets[82]), - libraryFilterNovelUnreadType: reader.readLongOrNull(offsets[83]), - libraryLocalSource: reader.readBoolOrNull(offsets[84]), - libraryShowCategoryTabs: reader.readBoolOrNull(offsets[85]), - libraryShowContinueReadingButton: reader.readBoolOrNull(offsets[86]), - libraryShowLanguage: reader.readBoolOrNull(offsets[87]), - libraryShowNumbersOfItems: reader.readBoolOrNull(offsets[88]), - localFolders: reader.readStringList(offsets[89]), + incognitoMode: reader.readBoolOrNull(offsets[71]), + lastTrackerLibraryLocation: reader.readStringOrNull(offsets[72]), + libraryDownloadedChapters: reader.readBoolOrNull(offsets[73]), + libraryFilterAnimeBookMarkedType: reader.readLongOrNull(offsets[74]), + libraryFilterAnimeDownloadType: reader.readLongOrNull(offsets[75]), + libraryFilterAnimeStartedType: reader.readLongOrNull(offsets[76]), + libraryFilterAnimeUnreadType: reader.readLongOrNull(offsets[77]), + libraryFilterMangasBookMarkedType: reader.readLongOrNull(offsets[78]), + libraryFilterMangasDownloadType: reader.readLongOrNull(offsets[79]), + libraryFilterMangasStartedType: reader.readLongOrNull(offsets[80]), + libraryFilterMangasUnreadType: reader.readLongOrNull(offsets[81]), + libraryFilterNovelBookMarkedType: reader.readLongOrNull(offsets[82]), + libraryFilterNovelDownloadType: reader.readLongOrNull(offsets[83]), + libraryFilterNovelStartedType: reader.readLongOrNull(offsets[84]), + libraryFilterNovelUnreadType: reader.readLongOrNull(offsets[85]), + libraryLocalSource: reader.readBoolOrNull(offsets[86]), + libraryShowCategoryTabs: reader.readBoolOrNull(offsets[87]), + libraryShowContinueReadingButton: reader.readBoolOrNull(offsets[88]), + libraryShowLanguage: reader.readBoolOrNull(offsets[89]), + libraryShowNumbersOfItems: reader.readBoolOrNull(offsets[90]), + localFolders: reader.readStringList(offsets[91]), mangaExtensionsRepo: reader.readObjectList( - offsets[91], + offsets[93], RepoSchema.deserialize, allOffsets, Repo(), ), - mangaGridSize: reader.readLongOrNull(offsets[92]), + mangaGridSize: reader.readLongOrNull(offsets[94]), mangaHomeDisplayType: _SettingsmangaHomeDisplayTypeValueEnumMap[reader.readByteOrNull( - offsets[93], + offsets[95], )] ?? DisplayType.comfortableGrid, - markEpisodeAsSeenType: reader.readLongOrNull(offsets[94]), - mergeLibraryNavMobile: reader.readBoolOrNull(offsets[95]), - navigationOrder: reader.readStringList(offsets[96]), + markEpisodeAsSeenType: reader.readLongOrNull(offsets[96]), + mergeLibraryNavMobile: reader.readBoolOrNull(offsets[97]), + navigationOrder: reader.readStringList(offsets[98]), novelDisplayType: _SettingsnovelDisplayTypeValueEnumMap[reader.readByteOrNull( - offsets[97], + offsets[99], )] ?? DisplayType.comfortableGrid, novelExtensionsRepo: reader.readObjectList( - offsets[98], + offsets[100], RepoSchema.deserialize, allOffsets, Repo(), ), - novelFontSize: reader.readLongOrNull(offsets[99]), - novelLibraryDownloadedChapters: reader.readBoolOrNull(offsets[101]), - novelLibraryLocalSource: reader.readBoolOrNull(offsets[102]), - novelLibraryShowCategoryTabs: reader.readBoolOrNull(offsets[103]), - novelLibraryShowContinueReadingButton: reader.readBoolOrNull(offsets[104]), - novelLibraryShowLanguage: reader.readBoolOrNull(offsets[105]), - novelLibraryShowNumbersOfItems: reader.readBoolOrNull(offsets[106]), - novelReaderLineHeight: reader.readDoubleOrNull(offsets[107]), - novelReaderPadding: reader.readLongOrNull(offsets[108]), - novelReaderTextColor: reader.readStringOrNull(offsets[109]), - novelReaderTheme: reader.readStringOrNull(offsets[110]), - novelRemoveExtraParagraphSpacing: reader.readBoolOrNull(offsets[111]), - novelShowScrollPercentage: reader.readBoolOrNull(offsets[112]), - novelTapToScroll: reader.readBoolOrNull(offsets[113]), + novelFontSize: reader.readLongOrNull(offsets[101]), + novelLibraryDownloadedChapters: reader.readBoolOrNull(offsets[103]), + novelLibraryLocalSource: reader.readBoolOrNull(offsets[104]), + novelLibraryShowCategoryTabs: reader.readBoolOrNull(offsets[105]), + novelLibraryShowContinueReadingButton: reader.readBoolOrNull(offsets[106]), + novelLibraryShowLanguage: reader.readBoolOrNull(offsets[107]), + novelLibraryShowNumbersOfItems: reader.readBoolOrNull(offsets[108]), + novelReaderLineHeight: reader.readDoubleOrNull(offsets[109]), + novelReaderPadding: reader.readLongOrNull(offsets[110]), + novelReaderTextColor: reader.readStringOrNull(offsets[111]), + novelReaderTheme: reader.readStringOrNull(offsets[112]), + novelRemoveExtraParagraphSpacing: reader.readBoolOrNull(offsets[113]), + novelShowScrollPercentage: reader.readBoolOrNull(offsets[114]), + novelTapToScroll: reader.readBoolOrNull(offsets[115]), novelTextAlign: _SettingsnovelTextAlignValueEnumMap[reader.readByteOrNull( - offsets[114], + offsets[116], )] ?? NovelTextAlign.left, - onlyIncludePinnedSources: reader.readBoolOrNull(offsets[115]), - pagePreloadAmount: reader.readLongOrNull(offsets[116]), + onlyIncludePinnedSources: reader.readBoolOrNull(offsets[117]), + pagePreloadAmount: reader.readLongOrNull(offsets[118]), personalPageModeList: reader.readObjectList( - offsets[117], + offsets[119], PersonalPageModeSchema.deserialize, allOffsets, PersonalPageMode(), ), personalReaderModeList: reader.readObjectList( - offsets[118], + offsets[120], PersonalReaderModeSchema.deserialize, allOffsets, PersonalReaderMode(), ), playerSubtitleSettings: reader.readObjectOrNull( - offsets[119], + offsets[121], PlayerSubtitleSettingsSchema.deserialize, allOffsets, ), - pureBlackDarkMode: reader.readBoolOrNull(offsets[120]), - relativeTimesTamps: reader.readLongOrNull(offsets[121]), - rpcShowCoverImage: reader.readBoolOrNull(offsets[122]), - rpcShowReadingWatchingProgress: reader.readBoolOrNull(offsets[123]), - rpcShowTitle: reader.readBoolOrNull(offsets[124]), - saveAsCBZArchive: reader.readBoolOrNull(offsets[125]), + pureBlackDarkMode: reader.readBoolOrNull(offsets[122]), + relativeTimesTamps: reader.readLongOrNull(offsets[123]), + rpcShowCoverImage: reader.readBoolOrNull(offsets[124]), + rpcShowReadingWatchingProgress: reader.readBoolOrNull(offsets[125]), + rpcShowTitle: reader.readBoolOrNull(offsets[126]), + saveAsCBZArchive: reader.readBoolOrNull(offsets[127]), scaleType: - _SettingsscaleTypeValueEnumMap[reader.readByteOrNull(offsets[126])] ?? + _SettingsscaleTypeValueEnumMap[reader.readByteOrNull(offsets[128])] ?? ScaleType.fitScreen, - showPagesNumber: reader.readBoolOrNull(offsets[127]), + showPagesNumber: reader.readBoolOrNull(offsets[129]), sortChapterList: reader.readObjectList( - offsets[128], + offsets[130], SortChapterSchema.deserialize, allOffsets, SortChapter(), ), sortLibraryAnime: reader.readObjectOrNull( - offsets[129], - SortLibraryMangaSchema.deserialize, - allOffsets, - ), - sortLibraryManga: reader.readObjectOrNull( - offsets[130], - SortLibraryMangaSchema.deserialize, - allOffsets, - ), - sortLibraryNovel: reader.readObjectOrNull( offsets[131], SortLibraryMangaSchema.deserialize, allOffsets, ), - startDatebackup: reader.readLongOrNull(offsets[132]), - themeIsDark: reader.readBoolOrNull(offsets[133]), - updateProgressAfterReading: reader.readBoolOrNull(offsets[134]), - updatedAt: reader.readLongOrNull(offsets[135]), - useLibass: reader.readBoolOrNull(offsets[136]), - useMpvConfig: reader.readBoolOrNull(offsets[137]), - usePageTapZones: reader.readBoolOrNull(offsets[138]), - useYUV420P: reader.readBoolOrNull(offsets[139]), - userAgent: reader.readStringOrNull(offsets[140]), - volumeBoostCap: reader.readLongOrNull(offsets[141]), + sortLibraryManga: reader.readObjectOrNull( + offsets[132], + SortLibraryMangaSchema.deserialize, + allOffsets, + ), + sortLibraryNovel: reader.readObjectOrNull( + offsets[133], + SortLibraryMangaSchema.deserialize, + allOffsets, + ), + startDatebackup: reader.readLongOrNull(offsets[134]), + themeIsDark: reader.readBoolOrNull(offsets[135]), + updateProgressAfterReading: reader.readBoolOrNull(offsets[136]), + updatedAt: reader.readLongOrNull(offsets[137]), + useLibass: reader.readBoolOrNull(offsets[138]), + useMpvConfig: reader.readBoolOrNull(offsets[139]), + usePageTapZones: reader.readBoolOrNull(offsets[140]), + useYUV420P: reader.readBoolOrNull(offsets[141]), + userAgent: reader.readStringOrNull(offsets[142]), + volumeBoostCap: reader.readLongOrNull(offsets[143]), ); object.chapterFilterBookmarkedList = reader .readObjectList( @@ -1819,17 +1833,17 @@ Settings _settingsDeserialize( allOffsets, ); object.filterScanlatorList = reader.readObjectList( - offsets[59], + offsets[61], FilterScanlatorSchema.deserialize, allOffsets, FilterScanlator(), ); object.locale = reader.readObjectOrNull( - offsets[90], + offsets[92], L10nLocaleSchema.deserialize, allOffsets, ); - object.novelGridSize = reader.readLongOrNull(offsets[100]); + object.novelGridSize = reader.readLongOrNull(offsets[102]); return object; } @@ -2028,13 +2042,13 @@ P _settingsDeserializeProp

( DisplayType.compactGrid) as P; case 47: - return (reader.readLongOrNull(offset)) as P; + return (reader.readBoolOrNull(offset)) as P; case 48: - return (reader.readStringOrNull(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 49: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 50: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readStringOrNull(offset)) as P; case 51: return (reader.readBoolOrNull(offset)) as P; case 52: @@ -2052,6 +2066,10 @@ P _settingsDeserializeProp

( case 58: return (reader.readBoolOrNull(offset)) as P; case 59: + return (reader.readBoolOrNull(offset)) as P; + case 60: + return (reader.readBoolOrNull(offset)) as P; + case 61: return (reader.readObjectList( offset, FilterScanlatorSchema.deserialize, @@ -2059,14 +2077,10 @@ P _settingsDeserializeProp

( FilterScanlator(), )) as P; - case 60: - return (reader.readDoubleOrNull(offset)) as P; - case 61: - return (reader.readLongOrNull(offset)) as P; case 62: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readDoubleOrNull(offset)) as P; case 63: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 64: return (reader.readBoolOrNull(offset)) as P; case 65: @@ -2074,19 +2088,19 @@ P _settingsDeserializeProp

( case 66: return (reader.readBoolOrNull(offset)) as P; case 67: - return (reader.readStringList(offset)) as P; - case 68: - return (reader.readStringOrNull(offset)) as P; - case 69: return (reader.readBoolOrNull(offset)) as P; + case 68: + return (reader.readBoolOrNull(offset)) as P; + case 69: + return (reader.readStringList(offset)) as P; case 70: return (reader.readStringOrNull(offset)) as P; case 71: return (reader.readBoolOrNull(offset)) as P; case 72: - return (reader.readLongOrNull(offset)) as P; + return (reader.readStringOrNull(offset)) as P; case 73: - return (reader.readLongOrNull(offset)) as P; + return (reader.readBoolOrNull(offset)) as P; case 74: return (reader.readLongOrNull(offset)) as P; case 75: @@ -2108,9 +2122,9 @@ P _settingsDeserializeProp

( case 83: return (reader.readLongOrNull(offset)) as P; case 84: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 85: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 86: return (reader.readBoolOrNull(offset)) as P; case 87: @@ -2118,15 +2132,19 @@ P _settingsDeserializeProp

( case 88: return (reader.readBoolOrNull(offset)) as P; case 89: - return (reader.readStringList(offset)) as P; + return (reader.readBoolOrNull(offset)) as P; case 90: + return (reader.readBoolOrNull(offset)) as P; + case 91: + return (reader.readStringList(offset)) as P; + case 92: return (reader.readObjectOrNull( offset, L10nLocaleSchema.deserialize, allOffsets, )) as P; - case 91: + case 93: return (reader.readObjectList( offset, RepoSchema.deserialize, @@ -2134,27 +2152,27 @@ P _settingsDeserializeProp

( Repo(), )) as P; - case 92: + case 94: return (reader.readLongOrNull(offset)) as P; - case 93: + case 95: return (_SettingsmangaHomeDisplayTypeValueEnumMap[reader.readByteOrNull( offset, )] ?? DisplayType.comfortableGrid) as P; - case 94: - return (reader.readLongOrNull(offset)) as P; - case 95: - return (reader.readBoolOrNull(offset)) as P; case 96: - return (reader.readStringList(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 97: + return (reader.readBoolOrNull(offset)) as P; + case 98: + return (reader.readStringList(offset)) as P; + case 99: return (_SettingsnovelDisplayTypeValueEnumMap[reader.readByteOrNull( offset, )] ?? DisplayType.comfortableGrid) as P; - case 98: + case 100: return (reader.readObjectList( offset, RepoSchema.deserialize, @@ -2162,14 +2180,10 @@ P _settingsDeserializeProp

( Repo(), )) as P; - case 99: - return (reader.readLongOrNull(offset)) as P; - case 100: - return (reader.readLongOrNull(offset)) as P; case 101: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 102: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 103: return (reader.readBoolOrNull(offset)) as P; case 104: @@ -2179,30 +2193,34 @@ P _settingsDeserializeProp

( case 106: return (reader.readBoolOrNull(offset)) as P; case 107: - return (reader.readDoubleOrNull(offset)) as P; + return (reader.readBoolOrNull(offset)) as P; case 108: - return (reader.readLongOrNull(offset)) as P; + return (reader.readBoolOrNull(offset)) as P; case 109: - return (reader.readStringOrNull(offset)) as P; + return (reader.readDoubleOrNull(offset)) as P; case 110: - return (reader.readStringOrNull(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 111: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readStringOrNull(offset)) as P; case 112: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readStringOrNull(offset)) as P; case 113: return (reader.readBoolOrNull(offset)) as P; case 114: + return (reader.readBoolOrNull(offset)) as P; + case 115: + return (reader.readBoolOrNull(offset)) as P; + case 116: return (_SettingsnovelTextAlignValueEnumMap[reader.readByteOrNull( offset, )] ?? NovelTextAlign.left) as P; - case 115: - return (reader.readBoolOrNull(offset)) as P; - case 116: - return (reader.readLongOrNull(offset)) as P; case 117: + return (reader.readBoolOrNull(offset)) as P; + case 118: + return (reader.readLongOrNull(offset)) as P; + case 119: return (reader.readObjectList( offset, PersonalPageModeSchema.deserialize, @@ -2210,7 +2228,7 @@ P _settingsDeserializeProp

( PersonalPageMode(), )) as P; - case 118: + case 120: return (reader.readObjectList( offset, PersonalReaderModeSchema.deserialize, @@ -2218,32 +2236,32 @@ P _settingsDeserializeProp

( PersonalReaderMode(), )) as P; - case 119: + case 121: return (reader.readObjectOrNull( offset, PlayerSubtitleSettingsSchema.deserialize, allOffsets, )) as P; - case 120: - return (reader.readBoolOrNull(offset)) as P; - case 121: - return (reader.readLongOrNull(offset)) as P; case 122: return (reader.readBoolOrNull(offset)) as P; case 123: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 124: return (reader.readBoolOrNull(offset)) as P; case 125: return (reader.readBoolOrNull(offset)) as P; case 126: - return (_SettingsscaleTypeValueEnumMap[reader.readByteOrNull(offset)] ?? - ScaleType.fitScreen) - as P; + return (reader.readBoolOrNull(offset)) as P; case 127: return (reader.readBoolOrNull(offset)) as P; case 128: + return (_SettingsscaleTypeValueEnumMap[reader.readByteOrNull(offset)] ?? + ScaleType.fitScreen) + as P; + case 129: + return (reader.readBoolOrNull(offset)) as P; + case 130: return (reader.readObjectList( offset, SortChapterSchema.deserialize, @@ -2251,20 +2269,6 @@ P _settingsDeserializeProp

( SortChapter(), )) as P; - case 129: - return (reader.readObjectOrNull( - offset, - SortLibraryMangaSchema.deserialize, - allOffsets, - )) - as P; - case 130: - return (reader.readObjectOrNull( - offset, - SortLibraryMangaSchema.deserialize, - allOffsets, - )) - as P; case 131: return (reader.readObjectOrNull( offset, @@ -2273,24 +2277,38 @@ P _settingsDeserializeProp

( )) as P; case 132: - return (reader.readLongOrNull(offset)) as P; + return (reader.readObjectOrNull( + offset, + SortLibraryMangaSchema.deserialize, + allOffsets, + )) + as P; case 133: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readObjectOrNull( + offset, + SortLibraryMangaSchema.deserialize, + allOffsets, + )) + as P; case 134: - return (reader.readBoolOrNull(offset)) as P; - case 135: return (reader.readLongOrNull(offset)) as P; + case 135: + return (reader.readBoolOrNull(offset)) as P; case 136: return (reader.readBoolOrNull(offset)) as P; case 137: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 138: return (reader.readBoolOrNull(offset)) as P; case 139: return (reader.readBoolOrNull(offset)) as P; case 140: - return (reader.readStringOrNull(offset)) as P; + return (reader.readBoolOrNull(offset)) as P; case 141: + return (reader.readBoolOrNull(offset)) as P; + case 142: + return (reader.readStringOrNull(offset)) as P; + case 143: return (reader.readLongOrNull(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); @@ -6020,6 +6038,108 @@ extension SettingsQueryFilter }); } + QueryBuilder doHEnabledIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'doHEnabled'), + ); + }); + } + + QueryBuilder + doHEnabledIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'doHEnabled'), + ); + }); + } + + QueryBuilder doHEnabledEqualTo( + bool? value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'doHEnabled', value: value), + ); + }); + } + + QueryBuilder + doHProviderIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'doHProviderId'), + ); + }); + } + + QueryBuilder + doHProviderIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'doHProviderId'), + ); + }); + } + + QueryBuilder doHProviderIdEqualTo( + int? value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'doHProviderId', value: value), + ); + }); + } + + QueryBuilder + doHProviderIdGreaterThan(int? value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'doHProviderId', + value: value, + ), + ); + }); + } + + QueryBuilder doHProviderIdLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'doHProviderId', + value: value, + ), + ); + }); + } + + QueryBuilder doHProviderIdBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'doHProviderId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + QueryBuilder doubleTapAnimationSpeedIsNull() { return QueryBuilder.apply(this, (query) { @@ -12612,6 +12732,30 @@ extension SettingsQuerySortBy on QueryBuilder { }); } + QueryBuilder sortByDoHEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'doHEnabled', Sort.asc); + }); + } + + QueryBuilder sortByDoHEnabledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'doHEnabled', Sort.desc); + }); + } + + QueryBuilder sortByDoHProviderId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'doHProviderId', Sort.asc); + }); + } + + QueryBuilder sortByDoHProviderIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'doHProviderId', Sort.desc); + }); + } + QueryBuilder sortByDoubleTapAnimationSpeed() { return QueryBuilder.apply(this, (query) { @@ -14133,6 +14277,30 @@ extension SettingsQuerySortThenBy }); } + QueryBuilder thenByDoHEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'doHEnabled', Sort.asc); + }); + } + + QueryBuilder thenByDoHEnabledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'doHEnabled', Sort.desc); + }); + } + + QueryBuilder thenByDoHProviderId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'doHProviderId', Sort.asc); + }); + } + + QueryBuilder thenByDoHProviderIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'doHProviderId', Sort.desc); + }); + } + QueryBuilder thenByDoubleTapAnimationSpeed() { return QueryBuilder.apply(this, (query) { @@ -15463,6 +15631,18 @@ extension SettingsQueryWhereDistinct }); } + QueryBuilder distinctByDoHEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'doHEnabled'); + }); + } + + QueryBuilder distinctByDoHProviderId() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'doHProviderId'); + }); + } + QueryBuilder distinctByDoubleTapAnimationSpeed() { return QueryBuilder.apply(this, (query) { @@ -16353,6 +16533,18 @@ extension SettingsQueryProperty }); } + QueryBuilder doHEnabledProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'doHEnabled'); + }); + } + + QueryBuilder doHProviderIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'doHProviderId'); + }); + } + QueryBuilder doubleTapAnimationSpeedProperty() { return QueryBuilder.apply(this, (query) { diff --git a/lib/modules/more/settings/general/general_screen.dart b/lib/modules/more/settings/general/general_screen.dart index ce86bf11..8ff30b67 100644 --- a/lib/modules/more/settings/general/general_screen.dart +++ b/lib/modules/more/settings/general/general_screen.dart @@ -6,7 +6,9 @@ import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/modules/more/providers/algorithm_weights_state_provider.dart'; import 'package:mangayomi/modules/more/settings/general/providers/general_state_provider.dart'; import 'package:mangayomi/providers/l10n_providers.dart'; +import 'package:mangayomi/modules/more/settings/general/providers/doh_provider_notifier.dart'; import 'package:mangayomi/utils/extensions/build_context_extensions.dart'; +import 'package:super_sliver_list/super_sliver_list.dart'; class GeneralScreen extends ConsumerStatefulWidget { const GeneralScreen({super.key}); @@ -45,11 +47,91 @@ class _GeneralStateScreen extends ConsumerState { ); final rpcShowTitleState = ref.watch(rpcShowTitleStateProvider); final rpcShowCoverImage = ref.watch(rpcShowCoverImageStateProvider); + final doHState = ref.watch(doHProviderStateProvider); + final availableProviders = ref.watch(availableDoHProvidersProvider); return Scaffold( appBar: AppBar(title: Text(l10n!.general)), body: SingleChildScrollView( child: Column( children: [ + ExpansionTile( + title: Text(l10n.dns_over_https), + initiallyExpanded: doHState.enabled, + trailing: IgnorePointer( + child: Switch(value: doHState.enabled, onChanged: (_) {}), + ), + onExpansionChanged: (value) => ref + .read(doHProviderStateProvider.notifier) + .setDoHEnabled(value), + children: [ + ListTile( + title: Text(l10n.dns_provider), + subtitle: Text( + availableProviders[doHState.providerId ?? 1].name, + style: TextStyle( + fontSize: 11, + color: context.secondaryColor, + ), + ), + onTap: () { + final providerId = doHState.providerId ?? 1; + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text(l10n.dns_provider), + content: SizedBox( + width: context.width(0.8), + child: RadioGroup( + groupValue: providerId, + onChanged: (value) { + ref + .read(doHProviderStateProvider.notifier) + .setDoHProvider(value!); + if (context.mounted) { + Navigator.pop(context); + } + }, + child: SuperListView.builder( + shrinkWrap: true, + itemCount: availableProviders.length, + itemBuilder: (context, index) { + final provider = availableProviders[index]; + return RadioListTile( + dense: true, + contentPadding: const EdgeInsets.all(0), + value: provider.id, + title: Text(provider.name), + ); + }, + ), + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () async { + Navigator.pop(context); + }, + child: Text( + l10n.cancel, + style: TextStyle( + color: context.primaryColor, + ), + ), + ), + ], + ), + ], + ); + }, + ); + }, + ), + ], + ), ListTile( onTap: () => _showCustomDnsDialog(context, ref, customDns), title: Text(l10n.custom_dns), diff --git a/lib/modules/more/settings/general/providers/doh_provider_notifier.dart b/lib/modules/more/settings/general/providers/doh_provider_notifier.dart new file mode 100644 index 00000000..7e8fb6c0 --- /dev/null +++ b/lib/modules/more/settings/general/providers/doh_provider_notifier.dart @@ -0,0 +1,65 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mangayomi/main.dart'; +import 'package:mangayomi/models/settings.dart'; +import 'package:mangayomi/services/http/doh/doh_providers.dart'; + +class DoHProviderState { + final bool enabled; + final int? providerId; + + DoHProviderState({required this.enabled, this.providerId}); + + DoHProviderState copyWith({bool? enabled, int? providerId}) { + return DoHProviderState( + enabled: enabled ?? this.enabled, + providerId: providerId ?? this.providerId, + ); + } +} + +final doHProviderStateProvider = + NotifierProvider(() { + return DoHProviderNotifier(); + }); + +class DoHProviderNotifier extends Notifier { + @override + DoHProviderState build() { + final settings = isar.settings.getSync(227)!; + return DoHProviderState( + enabled: settings.doHEnabled ?? false, + providerId: settings.doHProviderId, + ); + } + + void setDoHEnabled(bool enabled) { + final settings = isar.settings.getSync(227)!; + settings.doHEnabled = enabled; + if (enabled && settings.doHProviderId == null) { + settings.doHProviderId = 0; + } + isar.writeTxnSync(() { + isar.settings.putSync(settings); + }); + state = state.copyWith(enabled: enabled); + } + + void setDoHProvider(int providerId) { + final provider = DoHProviders.byId[providerId]; + if (provider == null) { + return; + } + + final settings = isar.settings.getSync(227)!; + settings.doHProviderId = providerId; + settings.doHEnabled = true; + isar.writeTxnSync(() { + isar.settings.putSync(settings); + }); + state = state.copyWith(enabled: true, providerId: providerId); + } +} + +final availableDoHProvidersProvider = Provider>((ref) { + return DoHProviders.all; +}); diff --git a/lib/services/http/doh/doh_providers.dart b/lib/services/http/doh/doh_providers.dart new file mode 100644 index 00000000..8cab3afe --- /dev/null +++ b/lib/services/http/doh/doh_providers.dart @@ -0,0 +1,209 @@ +/// Represents a DoH provider with URL and bootstrap DNS hosts +class DoHProvider { + /// Provider identifier (for persistence) + final int id; + + /// User-friendly name + final String name; + + /// DoH endpoint URL + final String url; + + /// Bootstrap DNS hosts (IPs) to avoid circular resolution + /// Mix of IPv4 and IPv6 for fallback + final List bootstrapIPs; + + /// Description for UI + final String description; + + /// Region/category info + final String region; + + const DoHProvider({ + required this.id, + required this.name, + required this.url, + required this.bootstrapIPs, + required this.description, + required this.region, + }); + + @override + String toString() => name; +} + +/// All available DoH providers +final class DoHProviders { + /// Cloudflare DNS - Fast, privacy-first + static const cloudflare = DoHProvider( + id: 0, + name: 'Cloudflare', + url: 'https://cloudflare-dns.com/dns-query', + bootstrapIPs: [ + '162.159.36.1', + '162.159.46.1', + '1.1.1.1', + '1.0.0.1', + '162.159.132.53', + '2606:4700:4700::1111', + '2606:4700:4700::1001', + '2606:4700:4700::0064', + '2606:4700:4700::6400', + ], + description: 'Fast and privacy-first DNS', + region: 'Global', + ); + + /// Google Public DNS - Reliable, fast + static const google = DoHProvider( + id: 1, + name: 'Google', + url: 'https://dns.google/dns-query', + bootstrapIPs: [ + '8.8.4.4', + '8.8.8.8', + '2001:4860:4860::8888', + '2001:4860:4860::8844', + ], + description: 'Google Public DNS', + region: 'Global', + ); + + /// AdGuard DNS Unfiltered - No blocklists by default + static const adguard = DoHProvider( + id: 2, + name: 'AdGuard', + url: 'https://dns-unfiltered.adguard.com/dns-query', + bootstrapIPs: [ + '94.140.14.140', + '94.140.14.141', + '2a10:50c0::1:ff', + '2a10:50c0::2:ff', + ], + description: 'Unfiltered (no blocking)', + region: 'Global', + ); + + /// Quad9 DNS - Privacy + security + static const quad9 = DoHProvider( + id: 3, + name: 'Quad9', + url: 'https://dns.quad9.net/dns-query', + bootstrapIPs: ['9.9.9.9', '149.112.112.112', '2620:fe::fe', '2620:fe::9'], + description: 'Privacy-focused with security', + region: 'Global', + ); + + /// AliDNS - Optimized for Asia + static const alidns = DoHProvider( + id: 4, + name: 'AliDNS', + url: 'https://dns.alidns.com/dns-query', + bootstrapIPs: [ + '223.5.5.5', + '223.6.6.6', + '2400:3200::1', + '2400:3200:baba::1', + ], + description: 'Optimized for Asia', + region: 'China', + ); + + /// DNSPod DNS - China mainland + static const dnspod = DoHProvider( + id: 5, + name: 'DNSPod', + url: 'https://doh.pub/dns-query', + bootstrapIPs: ['1.12.12.12', '120.53.53.53'], + description: 'China mainland DNS', + region: 'China', + ); + + /// 360 DNS - China + static const dns360 = DoHProvider( + id: 6, + name: '360 DNS', + url: 'https://doh.360.cn/dns-query', + bootstrapIPs: [ + '101.226.4.6', + '218.30.118.6', + '123.125.81.6', + '140.207.198.6', + '180.163.249.75', + '101.199.113.208', + '36.99.170.86', + ], + description: 'China domestic DNS', + region: 'China', + ); + + /// Quad101 - Taiwan + static const quad101 = DoHProvider( + id: 7, + name: 'Quad101', + url: 'https://dns.twnic.tw/dns-query', + bootstrapIPs: ['101.101.101.101', '2001:de4::101', '2001:de4::102'], + description: 'Taiwan DNS service', + region: 'Taiwan', + ); + + /// Mullvad DNS - Privacy-focused VPN provider + static const mullvad = DoHProvider( + id: 8, + name: 'Mullvad', + url: 'https://dns.mullvad.net/dns-query', + bootstrapIPs: ['194.242.2.2', '2a07:e340::2'], + description: 'Privacy-focused VPN provider', + region: 'Global', + ); + + /// ControlD DNS - Unfiltered free option + static const controld = DoHProvider( + id: 9, + name: 'ControlD', + url: 'https://freedns.controld.com/p0', + bootstrapIPs: ['76.76.2.0', '76.76.10.0', '2606:1a40::', '2606:1a40:1::'], + description: 'Unfiltered (free option)', + region: 'Global', + ); + + /// Njalla DNS - Non-logging, uncensored + static const njalla = DoHProvider( + id: 10, + name: 'Njalla', + url: 'https://dns.njal.la/dns-query', + bootstrapIPs: ['95.215.19.53', '2001:67c:2354:2::53'], + description: 'Non-logging and uncensored', + region: 'Global', + ); + + /// Shecan DNS - Iran censorship bypass + static const shecan = DoHProvider( + id: 11, + name: 'Shecan', + url: 'https://free.shecan.ir/dns-query', + bootstrapIPs: ['178.22.122.100', '185.51.200.2'], + description: 'Iran censorship bypass', + region: 'Iran', + ); + + /// All providers in a list + static const List all = [ + cloudflare, + google, + adguard, + quad9, + alidns, + dnspod, + dns360, + quad101, + mullvad, + controld, + njalla, + shecan, + ]; + + static final Map byId = { + for (final provider in all) provider.id: provider, + }; +} diff --git a/lib/services/http/doh/doh_resolver.dart b/lib/services/http/doh/doh_resolver.dart new file mode 100644 index 00000000..e545662a --- /dev/null +++ b/lib/services/http/doh/doh_resolver.dart @@ -0,0 +1,342 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:mangayomi/services/http/doh/doh_providers.dart'; + +/// Cache entry with dynamic TTL +class _CacheEntry { + final List ips; + final DateTime timestamp; + final int ttl; // in seconds + + _CacheEntry(this.ips, this.timestamp, {this.ttl = 300}); // default 5 min + + bool get isExpired => DateTime.now().difference(timestamp).inSeconds > ttl; +} + +/// Provider stats for circuit breaker +class _ProviderStats { + int successCount = 0; + int failureCount = 0; + DateTime lastFailure = DateTime.now(); + bool isCircuitOpen = false; + + double get successRate => (successCount + failureCount) == 0 + ? 100 + : (successCount / (successCount + failureCount)) * 100; + + bool shouldRetry() { + if (!isCircuitOpen) return true; + // Retry after 1 minute if circuit is open + return DateTime.now().difference(lastFailure).inSeconds > 60; + } +} + +/// DoH (DNS-over-HTTPS) Resolver +class DoHResolver { + /// Cache for resolved domains with dynamic TTL + static final Map _cache = {}; + + /// Provider statistics for circuit breaker + static final Map _providerStats = {}; + + /// Rate limiting (max requests per second per host) + static final Map> _requestHistory = {}; + static const int _maxRequestsPerSecond = 10; + + static const Duration _requestTimeout = Duration(seconds: 5); + + /// Resolve hostname using specified DoH provider with A and AAAA records + /// Uses bootstrap IPs to avoid circular DNS resolution + static Future> resolve( + String host, { + DoHProvider? provider, + bool ipv6 = true, + }) async { + // Use default (Cloudflare) if not specified + provider ??= DoHProviders.cloudflare; + + // Validate bootstrap IPs + if (provider.bootstrapIPs.isEmpty) { + _log('Provider ${provider.name} has no bootstrap IPs', isError: true); + return []; + } + + // Rate limiting check + if (!_checkRateLimit(host)) { + _log('Rate limit exceeded for $host', isError: true); + return []; + } + + // Check cache first (cache key includes both host and provider) + final cacheKey = '$host:${provider.id}'; + final cached = _cache[cacheKey]; + if (cached != null && !cached.isExpired) { + _log('Cache hit for $host on ${provider.name}'); + return cached.ips; + } + + try { + // Try resolution with specified provider + var ips = await _resolveFromProvider(host, provider); + + // If no IPv4, try IPv6 + if (ips.isEmpty && ipv6) { + _log('No IPv4 for $host, trying IPv6'); + ips = await _resolveFromProvider(host, provider, recordType: 'AAAA'); + } + + if (ips.isNotEmpty) { + _getProviderStats(provider.id).successCount++; + // Cache with dynamic TTL (default 5 min) + _cache[cacheKey] = _CacheEntry(ips, DateTime.now(), ttl: 300); + return ips; + } + + // If primary provider fails, try fallback + _log('Resolution failed with ${provider.name}, trying fallback'); + return await _resolveWithFallback(host, provider, ipv6: ipv6); + } catch (e) { + _getProviderStats(provider.id).failureCount++; + _getProviderStats(provider.id).lastFailure = DateTime.now(); + _log('Error resolving $host: $e', isError: true); + return []; + } + } + + /// Resolve using specific provider via bootstrap IP + /// This avoids circular DNS dependency by connecting to bootstrap IP directly + static Future> _resolveFromProvider( + String host, + DoHProvider provider, { + String recordType = 'A', + }) async { + // Check circuit breaker + final stats = _getProviderStats(provider.id); + if (!stats.shouldRetry()) { + _log('Provider ${provider.name} circuit breaker is open', isError: true); + return []; + } + + final uri = Uri.parse(provider.url); + final providerHost = uri.host; + + // Validate bootstrap IPs format + final validBootstrapIps = provider.bootstrapIPs + .where((ip) => _isValidIp(ip)) + .toList(); + if (validBootstrapIps.isEmpty) { + _log('No valid bootstrap IPs for ${provider.name}', isError: true); + return []; + } + + // Try each bootstrap IP until one succeeds + for (int i = 0; i < validBootstrapIps.length; i++) { + final bootstrapIp = validBootstrapIps[i]; + try { + final result = await _queryDoHViaBootstrapIp( + host, + provider, + bootstrapIp, + providerHost, + recordType, + ); + if (result.isNotEmpty) { + _log( + 'Resolved $host via ${provider.name} (bootstrap IP $i+1/${validBootstrapIps.length})', + ); + return result; + } + } catch (e) { + _log('Bootstrap IP $i failed for ${provider.name}: $e'); + continue; + } + } + + stats.failureCount++; + stats.lastFailure = DateTime.now(); + // Open circuit after 3 consecutive failures + if (stats.failureCount >= 3) { + stats.isCircuitOpen = true; + _log('Circuit breaker opened for ${provider.name}', isError: true); + } + + return []; + } + + /// Query DoH via specific bootstrap IP (direct IP connection, no DNS lookup) + static Future> _queryDoHViaBootstrapIp( + String targetHost, + DoHProvider provider, + String bootstrapIp, + String providerHost, + String recordType, + ) async { + final uri = Uri.parse(provider.url); + final client = HttpClient(); + client.connectionTimeout = _requestTimeout; + + try { + // Create request with bootstrap IP instead of hostname + final request = await client.getUrl( + Uri( + scheme: uri.scheme, + host: bootstrapIp, // Use bootstrap IP directly + port: uri.port, + path: uri.path, + query: 'name=$targetHost&type=$recordType', + ), + ); + + // Set Host header to the actual provider hostname (required for HTTPS SNI) + request.headers.set('Host', providerHost); + request.headers.set('Accept', 'application/dns-json'); + request.headers.set('User-Agent', 'Mangayomi/1.0'); + + final response = await request.close().timeout(_requestTimeout); + + if (response.statusCode == 200) { + final body = await utf8.decodeStream(response); + return _parseDoHResponse(body); + } + + _log('HTTP ${response.statusCode} from $bootstrapIp (${provider.name})'); + return []; + } on SocketException { + // Connection failed with this bootstrap IP + _log('Socket error on $bootstrapIp'); + rethrow; + } on TimeoutException { + // Timeout with this bootstrap IP + _log('Timeout on $bootstrapIp'); + rethrow; + } finally { + client.close(); + } + } + + /// Fallback resolution with other providers + static Future> _resolveWithFallback( + String host, + DoHProvider primary, { + bool ipv6 = true, + }) async { + // Try other providers in order + for (final provider in DoHProviders.all) { + if (provider.id == primary.id) continue; + + // Skip providers with open circuit + final stats = _getProviderStats(provider.id); + if (!stats.shouldRetry()) { + _log('Skipping ${provider.name} (circuit open)'); + continue; + } + + try { + var ips = await _resolveFromProvider(host, provider); + + if (ips.isEmpty && ipv6) { + ips = await _resolveFromProvider(host, provider, recordType: 'AAAA'); + } + + if (ips.isNotEmpty) { + stats.successCount++; + final cacheKey = '$host:${provider.id}'; + _cache[cacheKey] = _CacheEntry(ips, DateTime.now(), ttl: 300); + _log('Fallback success with ${provider.name}'); + return ips; + } + } catch (e) { + _log('Fallback failed with ${provider.name}: $e'); + continue; + } + } + + _log('All providers exhausted for $host', isError: true); + return []; + } + + /// Parse DoH JSON response (RFC 8484 format) and extract TTL + static List _parseDoHResponse(String jsonBody) { + try { + final json = jsonDecode(jsonBody) as Map; + final answers = json['Answer'] as List?; + + if (answers != null && answers.isNotEmpty) { + final ips = answers + .map((answer) { + if (answer is Map && answer.containsKey('data')) { + return answer['data'] as String; + } + return null; + }) + .whereType() + .toList(); + + if (ips.isNotEmpty) { + // Extract TTL from first answer if available + final ttl = (answers.first is Map && answers.first.containsKey('TTL')) + ? (answers.first['TTL'] as int?) ?? 300 + : 300; + _log('Parsed ${ips.length} record(s) with TTL $ttl'); + } + + return ips; + } + } catch (e) { + _log('Failed to parse DoH response: $e', isError: true); + } + + return []; + } + + /// Check if IP address is valid (IPv4 or IPv6) + static bool _isValidIp(String ip) { + try { + InternetAddress(ip); + return true; + } catch (e) { + return false; + } + } + + /// Rate limiting: check if request is allowed + static bool _checkRateLimit(String host) { + final now = DateTime.now(); + final cutoffTime = now.subtract(Duration(seconds: 1)); + + // Get or create request history for this host + _requestHistory[host] ??= []; + final history = _requestHistory[host]!; + + // Remove old entries outside 1-second window + history.removeWhere((t) => t.isBefore(cutoffTime)); + + // Check if limit exceeded + if (history.length >= _maxRequestsPerSecond) { + return false; + } + + // Record new request + history.add(now); + return true; + } + + /// Get or create provider stats + static _ProviderStats _getProviderStats(int providerId) { + return _providerStats.putIfAbsent(providerId, () => _ProviderStats()); + } + + static void _log(String message, {bool isError = false}) { + final timestamp = DateTime.now().toIso8601String(); + if (kDebugMode) { + if (isError) { + print('❌ [$timestamp] DoH: $message'); + } else { + // ignore: avoid_print + print('✓ [$timestamp] DoH: $message'); + } + } + } +} diff --git a/lib/services/http/m_client.dart b/lib/services/http/m_client.dart index fbcff614..2f33457e 100644 --- a/lib/services/http/m_client.dart +++ b/lib/services/http/m_client.dart @@ -14,6 +14,8 @@ import 'package:http/io_client.dart'; import 'package:mangayomi/services/http/rhttp/src/model/settings.dart'; import 'package:mangayomi/utils/log/log.dart'; import 'package:mangayomi/services/http/rhttp/rhttp.dart' as rhttp; +import 'package:mangayomi/services/http/doh/doh_resolver.dart'; +import 'package:mangayomi/services/http/doh/doh_providers.dart'; class MClient { MClient(); @@ -56,18 +58,31 @@ class MClient { rhttp.ClientSettings? settings, bool showCloudFlareError = true, }) { - final clientSettings = customDns == null || customDns!.trim().isEmpty - ? settings - : settings?.copyWith( - dnsSettings: DnsSettings.dynamic( - resolver: (host) async => [customDns!], - ), - ) ?? - ClientSettings( - dnsSettings: DnsSettings.dynamic( - resolver: (host) async => [customDns!], - ), - ); + final appSettings = isar.settings.getSync(227); + final useDoH = appSettings?.doHEnabled ?? false; + final doHProviderId = appSettings?.doHProviderId; + + DnsSettings? dnsSettings; + + if (useDoH && doHProviderId != null) { + // Use DoH resolver with specific provider + final provider = DoHProviders.byId[doHProviderId]; + if (provider != null) { + dnsSettings = DnsSettings.dynamic( + resolver: (host) => DoHResolver.resolve(host, provider: provider), + ); + } + } else if (customDns != null && customDns!.trim().isNotEmpty) { + // Fallback to custom static DNS + dnsSettings = DnsSettings.dynamic(resolver: (host) async => [customDns!]); + } + + // Apply DNS settings if configured + final clientSettings = dnsSettings != null + ? settings?.copyWith(dnsSettings: dnsSettings) ?? + ClientSettings(dnsSettings: dnsSettings) + : settings; + return InterceptedClient.build( client: httpClient(settings: clientSettings, reqcopyWith: reqcopyWith), retryPolicy: ResolveCloudFlareChallenge(showCloudFlareError),