diff --git a/lib/eval/dart/bridge/m_chapter.dart b/lib/eval/dart/bridge/m_chapter.dart index c3160e7d..3b66e6e9 100644 --- a/lib/eval/dart/bridge/m_chapter.dart +++ b/lib/eval/dart/bridge/m_chapter.dart @@ -12,6 +12,11 @@ class MChapterBridge { url: namedArgs.get('url'), dateUpload: namedArgs.get('dateUpload'), scanlator: namedArgs.get('scanlator'), + isFiller: namedArgs.get('isFiller'), + thumbnailUrl: namedArgs.get('scanlator'), + description: namedArgs.get('scanlator'), + downloadSize: namedArgs.get('scanlator'), + duration: namedArgs.get('scanlator'), ); }, }, @@ -20,6 +25,11 @@ class MChapterBridge { 'url': (visitor, target) => (target as MChapter).url, 'dateUpload': (visitor, target) => (target as MChapter).dateUpload, 'scanlator': (visitor, target) => (target as MChapter).scanlator, + 'isFiller': (visitor, target) => (target as MChapter).isFiller, + 'thumbnailUrl': (visitor, target) => (target as MChapter).thumbnailUrl, + 'description': (visitor, target) => (target as MChapter).description, + 'downloadSize': (visitor, target) => (target as MChapter).downloadSize, + 'duration': (visitor, target) => (target as MChapter).duration, }, setters: { 'name': (visitor, target, value) => @@ -30,6 +40,16 @@ class MChapterBridge { (target as MChapter).dateUpload = value as String?, 'scanlator': (visitor, target, value) => (target as MChapter).scanlator = value as String?, + 'isFiller': (visitor, target, value) => + (target as MChapter).isFiller = value as bool?, + 'thumbnailUrl': (visitor, target, value) => + (target as MChapter).thumbnailUrl = value as String?, + 'description': (visitor, target, value) => + (target as MChapter).description = value as String?, + 'downloadSize': (visitor, target, value) => + (target as MChapter).downloadSize = value as String?, + 'duration': (visitor, target, value) => + (target as MChapter).duration = value as String?, }, ); void registerBridgedClasses(D4rt interpreter) { diff --git a/lib/eval/model/m_chapter.dart b/lib/eval/model/m_chapter.dart index 8a774db1..82fa94ef 100644 --- a/lib/eval/model/m_chapter.dart +++ b/lib/eval/model/m_chapter.dart @@ -6,13 +6,41 @@ class MChapter { String? dateUpload; String? scanlator; - MChapter({this.name, this.url, this.dateUpload, this.scanlator}); + + bool? isFiller; + + String? thumbnailUrl; + + String? description; + + /// video size + String? downloadSize; + + /// video duration + String? duration; + + MChapter({ + this.name, + this.url, + this.dateUpload, + this.scanlator, + this.isFiller = false, + this.thumbnailUrl, + this.description, + this.downloadSize, + this.duration, + }); factory MChapter.fromJson(Map json) { return MChapter( name: json['name'], url: json['url'], dateUpload: json['dateUpload'], scanlator: json['scanlator'], + isFiller: json['isFiller'] ?? false, + thumbnailUrl: json['thumbnailUrl'], + description: json['description'], + downloadSize: json['downloadSize'], + duration: json['duration'], ); } Map toJson() => { @@ -20,5 +48,10 @@ class MChapter { 'url': url, 'dateUpload': dateUpload, 'scanlator': scanlator, + 'isFiller': isFiller, + 'thumbnailUrl': thumbnailUrl, + 'description': description, + 'downloadSize': downloadSize, + 'duration': duration, }; } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 824fc2f4..dbb60d48 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -472,4 +472,7 @@ "sync_enable_histories": "Sync history data", "sync_enable_updates": "Sync update data", "sync_enable_settings": "Sync settings" + "anime4K": "Enable Anime4K", + "anime4K_info": "Supports .js scripts under /mpv/scripts/", + "anime4K_download": "MPV config files are required!\nDownload now?" } diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 5f9e105c..35aa9e34 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -2902,6 +2902,24 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Sync settings'** String get sync_enable_settings; + + /// No description provided for @anime4K. + /// + /// In en, this message translates to: + /// **'Enable Anime4K'** + String get anime4K; + + /// No description provided for @anime4K_info. + /// + /// In en, this message translates to: + /// **'Supports .js scripts under /mpv/scripts/'** + String get anime4K_info; + + /// No description provided for @anime4K_download. + /// + /// In en, this message translates to: + /// **'MPV config files are required!\nDownload now?'** + String get anime4K_download; } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_ar.dart b/lib/l10n/generated/app_localizations_ar.dart index 2cd15f8d..ca06afba 100644 --- a/lib/l10n/generated/app_localizations_ar.dart +++ b/lib/l10n/generated/app_localizations_ar.dart @@ -1494,4 +1494,14 @@ class AppLocalizationsAr extends AppLocalizations { @override String get sync_enable_settings => 'Sync settings'; + + @override + String get anime4K => 'Enable Anime4K'; + + @override + String get anime4K_info => 'Supports .js scripts under /mpv/scripts/'; + + @override + String get anime4K_download => + 'MPV config files are required!\nDownload now?'; } diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 37a2cbca..f1da1f28 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -1507,4 +1507,14 @@ class AppLocalizationsDe extends AppLocalizations { @override String get sync_enable_settings => 'Sync settings'; + + @override + String get anime4K => 'Enable Anime4K'; + + @override + String get anime4K_info => 'Supports .js scripts under /mpv/scripts/'; + + @override + String get anime4K_download => + 'MPV config files are required!\nDownload now?'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index b155ade0..0dc11fbf 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -1495,4 +1495,14 @@ class AppLocalizationsEn extends AppLocalizations { @override String get sync_enable_settings => 'Sync settings'; + + @override + String get anime4K => 'Enable Anime4K'; + + @override + String get anime4K_info => 'Supports .js scripts under /mpv/scripts/'; + + @override + String get anime4K_download => + 'MPV config files are required!\nDownload now?'; } diff --git a/lib/l10n/generated/app_localizations_es.dart b/lib/l10n/generated/app_localizations_es.dart index 02baacb3..8c62a389 100644 --- a/lib/l10n/generated/app_localizations_es.dart +++ b/lib/l10n/generated/app_localizations_es.dart @@ -1512,6 +1512,16 @@ class AppLocalizationsEs extends AppLocalizations { @override String get sync_enable_settings => 'Sync settings'; + + @override + String get anime4K => 'Enable Anime4K'; + + @override + String get anime4K_info => 'Supports .js scripts under /mpv/scripts/'; + + @override + String get anime4K_download => + 'MPV config files are required!\nDownload now?'; } /// 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 34c81a28..ed40292e 100644 --- a/lib/l10n/generated/app_localizations_fr.dart +++ b/lib/l10n/generated/app_localizations_fr.dart @@ -1513,4 +1513,14 @@ class AppLocalizationsFr extends AppLocalizations { @override String get sync_enable_settings => 'Sync settings'; + + @override + String get anime4K => 'Enable Anime4K'; + + @override + String get anime4K_info => 'Supports .js scripts under /mpv/scripts/'; + + @override + String get anime4K_download => + 'MPV config files are required!\nDownload now?'; } diff --git a/lib/l10n/generated/app_localizations_id.dart b/lib/l10n/generated/app_localizations_id.dart index 71a677f8..ef88463e 100644 --- a/lib/l10n/generated/app_localizations_id.dart +++ b/lib/l10n/generated/app_localizations_id.dart @@ -1501,4 +1501,14 @@ class AppLocalizationsId extends AppLocalizations { @override String get sync_enable_settings => 'Sync settings'; + + @override + String get anime4K => 'Enable Anime4K'; + + @override + String get anime4K_info => 'Supports .js scripts under /mpv/scripts/'; + + @override + String get anime4K_download => + 'MPV config files are required!\nDownload now?'; } diff --git a/lib/l10n/generated/app_localizations_it.dart b/lib/l10n/generated/app_localizations_it.dart index f49ea921..97bcf6ef 100644 --- a/lib/l10n/generated/app_localizations_it.dart +++ b/lib/l10n/generated/app_localizations_it.dart @@ -1510,4 +1510,14 @@ class AppLocalizationsIt extends AppLocalizations { @override String get sync_enable_settings => 'Sync settings'; + + @override + String get anime4K => 'Enable Anime4K'; + + @override + String get anime4K_info => 'Supports .js scripts under /mpv/scripts/'; + + @override + String get anime4K_download => + 'MPV config files are required!\nDownload now?'; } diff --git a/lib/l10n/generated/app_localizations_pt.dart b/lib/l10n/generated/app_localizations_pt.dart index f84131f6..57712332 100644 --- a/lib/l10n/generated/app_localizations_pt.dart +++ b/lib/l10n/generated/app_localizations_pt.dart @@ -1509,6 +1509,16 @@ class AppLocalizationsPt extends AppLocalizations { @override String get sync_enable_settings => 'Sync settings'; + + @override + String get anime4K => 'Enable Anime4K'; + + @override + String get anime4K_info => 'Supports .js scripts under /mpv/scripts/'; + + @override + String get anime4K_download => + 'MPV config files are required!\nDownload now?'; } /// 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 ea8db99d..2e704fb6 100644 --- a/lib/l10n/generated/app_localizations_ru.dart +++ b/lib/l10n/generated/app_localizations_ru.dart @@ -1511,4 +1511,14 @@ class AppLocalizationsRu extends AppLocalizations { @override String get sync_enable_settings => 'Sync settings'; + + @override + String get anime4K => 'Enable Anime4K'; + + @override + String get anime4K_info => 'Supports .js scripts under /mpv/scripts/'; + + @override + String get anime4K_download => + 'MPV config files are required!\nDownload now?'; } diff --git a/lib/l10n/generated/app_localizations_th.dart b/lib/l10n/generated/app_localizations_th.dart index 48797005..aaed6589 100644 --- a/lib/l10n/generated/app_localizations_th.dart +++ b/lib/l10n/generated/app_localizations_th.dart @@ -1495,4 +1495,14 @@ class AppLocalizationsTh extends AppLocalizations { @override String get sync_enable_settings => 'Sync settings'; + + @override + String get anime4K => 'Enable Anime4K'; + + @override + String get anime4K_info => 'Supports .js scripts under /mpv/scripts/'; + + @override + String get anime4K_download => + 'MPV config files are required!\nDownload now?'; } diff --git a/lib/l10n/generated/app_localizations_tr.dart b/lib/l10n/generated/app_localizations_tr.dart index ec9837cd..1cce8a46 100644 --- a/lib/l10n/generated/app_localizations_tr.dart +++ b/lib/l10n/generated/app_localizations_tr.dart @@ -1501,4 +1501,14 @@ class AppLocalizationsTr extends AppLocalizations { @override String get sync_enable_settings => 'Sync settings'; + + @override + String get anime4K => 'Enable Anime4K'; + + @override + String get anime4K_info => 'Supports .js scripts under /mpv/scripts/'; + + @override + String get anime4K_download => + 'MPV config files are required!\nDownload now?'; } diff --git a/lib/l10n/generated/app_localizations_zh.dart b/lib/l10n/generated/app_localizations_zh.dart index c76e32b9..2e8702c5 100644 --- a/lib/l10n/generated/app_localizations_zh.dart +++ b/lib/l10n/generated/app_localizations_zh.dart @@ -1466,4 +1466,14 @@ class AppLocalizationsZh extends AppLocalizations { @override String get sync_enable_settings => 'Sync settings'; + + @override + String get anime4K => 'Enable Anime4K'; + + @override + String get anime4K_info => 'Supports .js scripts under /mpv/scripts/'; + + @override + String get anime4K_download => + 'MPV config files are required!\nDownload now?'; } diff --git a/lib/main.dart b/lib/main.dart index 32a5f03f..b1564c53 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -30,6 +30,7 @@ import 'package:mangayomi/utils/discord_rpc.dart'; import 'package:mangayomi/utils/url_protocol/api.dart'; import 'package:mangayomi/modules/more/settings/appearance/providers/theme_provider.dart'; import 'package:mangayomi/modules/library/providers/file_scanner.dart'; +// ignore: depend_on_referenced_packages import 'package:media_kit/media_kit.dart'; import 'package:path_provider/path_provider.dart'; import 'package:window_manager/window_manager.dart'; diff --git a/lib/models/chapter.dart b/lib/models/chapter.dart index a09cb415..d2d2c0e7 100644 --- a/lib/models/chapter.dart +++ b/lib/models/chapter.dart @@ -26,6 +26,18 @@ class Chapter { ///Only for local archive Comic String? archivePath; + bool? isFiller; + + String? thumbnailUrl; + + String? description; + + /// video size + String? downloadSize; + + /// video duration + String? duration; + int? updatedAt; final manga = IsarLink(); @@ -41,6 +53,11 @@ class Chapter { this.isRead = false, this.lastPageRead = '', this.archivePath = '', + this.isFiller = false, + this.thumbnailUrl, + this.description, + this.downloadSize, + this.duration, this.updatedAt = 0, }); @@ -55,6 +72,11 @@ class Chapter { name = json['name']; scanlator = json['scanlator']; url = json['url']; + isFiller = json['isFiller'] ?? false; + thumbnailUrl = json['thumbnailUrl']; + description = json['description']; + downloadSize = json['downloadSize']; + duration = json['duration']; updatedAt = json['updatedAt']; } @@ -69,6 +91,11 @@ class Chapter { 'name': name, 'scanlator': scanlator, 'url': url, + 'isFiller': isFiller, + 'thumbnailUrl': thumbnailUrl, + 'description': description, + 'downloadSize': downloadSize, + 'duration': duration, 'updatedAt': updatedAt ?? 0, }; } diff --git a/lib/models/chapter.g.dart b/lib/models/chapter.g.dart index 9bebb94f..acc08cac 100644 --- a/lib/models/chapter.g.dart +++ b/lib/models/chapter.g.dart @@ -27,43 +27,68 @@ const ChapterSchema = CollectionSchema( name: r'dateUpload', type: IsarType.string, ), - r'isBookmarked': PropertySchema( + r'description': PropertySchema( id: 2, + name: r'description', + type: IsarType.string, + ), + r'downloadSize': PropertySchema( + id: 3, + name: r'downloadSize', + type: IsarType.string, + ), + r'duration': PropertySchema( + id: 4, + name: r'duration', + type: IsarType.string, + ), + r'isBookmarked': PropertySchema( + id: 5, name: r'isBookmarked', type: IsarType.bool, ), + r'isFiller': PropertySchema( + id: 6, + name: r'isFiller', + type: IsarType.bool, + ), r'isRead': PropertySchema( - id: 3, + id: 7, name: r'isRead', type: IsarType.bool, ), r'lastPageRead': PropertySchema( - id: 4, + id: 8, name: r'lastPageRead', type: IsarType.string, ), r'mangaId': PropertySchema( - id: 5, + id: 9, name: r'mangaId', type: IsarType.long, ), r'name': PropertySchema( - id: 6, + id: 10, name: r'name', type: IsarType.string, ), r'scanlator': PropertySchema( - id: 7, + id: 11, name: r'scanlator', type: IsarType.string, ), + r'thumbnailUrl': PropertySchema( + id: 12, + name: r'thumbnailUrl', + type: IsarType.string, + ), r'updatedAt': PropertySchema( - id: 8, + id: 13, name: r'updatedAt', type: IsarType.long, ), r'url': PropertySchema( - id: 9, + id: 14, name: r'url', type: IsarType.string, ) @@ -107,6 +132,24 @@ int _chapterEstimateSize( bytesCount += 3 + value.length * 3; } } + { + final value = object.description; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.downloadSize; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.duration; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } { final value = object.lastPageRead; if (value != null) { @@ -125,6 +168,12 @@ int _chapterEstimateSize( bytesCount += 3 + value.length * 3; } } + { + final value = object.thumbnailUrl; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } { final value = object.url; if (value != null) { @@ -142,14 +191,19 @@ void _chapterSerialize( ) { writer.writeString(offsets[0], object.archivePath); writer.writeString(offsets[1], object.dateUpload); - writer.writeBool(offsets[2], object.isBookmarked); - writer.writeBool(offsets[3], object.isRead); - writer.writeString(offsets[4], object.lastPageRead); - writer.writeLong(offsets[5], object.mangaId); - writer.writeString(offsets[6], object.name); - writer.writeString(offsets[7], object.scanlator); - writer.writeLong(offsets[8], object.updatedAt); - writer.writeString(offsets[9], object.url); + writer.writeString(offsets[2], object.description); + writer.writeString(offsets[3], object.downloadSize); + writer.writeString(offsets[4], object.duration); + writer.writeBool(offsets[5], object.isBookmarked); + writer.writeBool(offsets[6], object.isFiller); + writer.writeBool(offsets[7], object.isRead); + writer.writeString(offsets[8], object.lastPageRead); + writer.writeLong(offsets[9], object.mangaId); + writer.writeString(offsets[10], object.name); + writer.writeString(offsets[11], object.scanlator); + writer.writeString(offsets[12], object.thumbnailUrl); + writer.writeLong(offsets[13], object.updatedAt); + writer.writeString(offsets[14], object.url); } Chapter _chapterDeserialize( @@ -161,15 +215,20 @@ Chapter _chapterDeserialize( final object = Chapter( archivePath: reader.readStringOrNull(offsets[0]), dateUpload: reader.readStringOrNull(offsets[1]), + description: reader.readStringOrNull(offsets[2]), + downloadSize: reader.readStringOrNull(offsets[3]), + duration: reader.readStringOrNull(offsets[4]), id: id, - isBookmarked: reader.readBoolOrNull(offsets[2]), - isRead: reader.readBoolOrNull(offsets[3]), - lastPageRead: reader.readStringOrNull(offsets[4]), - mangaId: reader.readLongOrNull(offsets[5]), - name: reader.readStringOrNull(offsets[6]), - scanlator: reader.readStringOrNull(offsets[7]), - updatedAt: reader.readLongOrNull(offsets[8]), - url: reader.readStringOrNull(offsets[9]), + isBookmarked: reader.readBoolOrNull(offsets[5]), + isFiller: reader.readBoolOrNull(offsets[6]), + isRead: reader.readBoolOrNull(offsets[7]), + lastPageRead: reader.readStringOrNull(offsets[8]), + mangaId: reader.readLongOrNull(offsets[9]), + name: reader.readStringOrNull(offsets[10]), + scanlator: reader.readStringOrNull(offsets[11]), + thumbnailUrl: reader.readStringOrNull(offsets[12]), + updatedAt: reader.readLongOrNull(offsets[13]), + url: reader.readStringOrNull(offsets[14]), ); return object; } @@ -186,20 +245,30 @@ P _chapterDeserializeProp

( case 1: return (reader.readStringOrNull(offset)) as P; case 2: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readStringOrNull(offset)) as P; case 3: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readStringOrNull(offset)) as P; case 4: return (reader.readStringOrNull(offset)) as P; case 5: - return (reader.readLongOrNull(offset)) as P; + return (reader.readBoolOrNull(offset)) as P; case 6: - return (reader.readStringOrNull(offset)) as P; + return (reader.readBoolOrNull(offset)) as P; case 7: - return (reader.readStringOrNull(offset)) as P; + return (reader.readBoolOrNull(offset)) as P; case 8: - return (reader.readLongOrNull(offset)) as P; + return (reader.readStringOrNull(offset)) as P; case 9: + return (reader.readLongOrNull(offset)) as P; + case 10: + return (reader.readStringOrNull(offset)) as P; + case 11: + return (reader.readStringOrNull(offset)) as P; + case 12: + return (reader.readStringOrNull(offset)) as P; + case 13: + return (reader.readLongOrNull(offset)) as P; + case 14: return (reader.readStringOrNull(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); @@ -589,6 +658,447 @@ extension ChapterQueryFilter }); } + QueryBuilder descriptionIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'description', + )); + }); + } + + QueryBuilder descriptionIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'description', + )); + }); + } + + QueryBuilder descriptionEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'description', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder descriptionGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'description', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder descriptionLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'description', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder descriptionBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'description', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder descriptionStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'description', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder descriptionEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'description', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder descriptionContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'description', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder descriptionMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'description', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder descriptionIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'description', + value: '', + )); + }); + } + + QueryBuilder + descriptionIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'description', + value: '', + )); + }); + } + + QueryBuilder downloadSizeIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'downloadSize', + )); + }); + } + + QueryBuilder + downloadSizeIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'downloadSize', + )); + }); + } + + QueryBuilder downloadSizeEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'downloadSize', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder downloadSizeGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'downloadSize', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder downloadSizeLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'downloadSize', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder downloadSizeBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'downloadSize', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder downloadSizeStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'downloadSize', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder downloadSizeEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'downloadSize', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder downloadSizeContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'downloadSize', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder downloadSizeMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'downloadSize', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder downloadSizeIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'downloadSize', + value: '', + )); + }); + } + + QueryBuilder + downloadSizeIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'downloadSize', + value: '', + )); + }); + } + + QueryBuilder durationIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'duration', + )); + }); + } + + QueryBuilder durationIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'duration', + )); + }); + } + + QueryBuilder durationEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'duration', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder durationGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'duration', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder durationLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'duration', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder durationBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'duration', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder durationStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'duration', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder durationEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'duration', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder durationContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'duration', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder durationMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'duration', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder durationIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'duration', + value: '', + )); + }); + } + + QueryBuilder durationIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'duration', + value: '', + )); + }); + } + QueryBuilder idIsNull() { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(const FilterCondition.isNull( @@ -684,6 +1194,32 @@ extension ChapterQueryFilter }); } + QueryBuilder isFillerIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'isFiller', + )); + }); + } + + QueryBuilder isFillerIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'isFiller', + )); + }); + } + + QueryBuilder isFillerEqualTo( + bool? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isFiller', + value: value, + )); + }); + } + QueryBuilder isReadIsNull() { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(const FilterCondition.isNull( @@ -1219,6 +1755,154 @@ extension ChapterQueryFilter }); } + QueryBuilder thumbnailUrlIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'thumbnailUrl', + )); + }); + } + + QueryBuilder + thumbnailUrlIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'thumbnailUrl', + )); + }); + } + + QueryBuilder thumbnailUrlEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'thumbnailUrl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder thumbnailUrlGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'thumbnailUrl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder thumbnailUrlLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'thumbnailUrl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder thumbnailUrlBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'thumbnailUrl', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder thumbnailUrlStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'thumbnailUrl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder thumbnailUrlEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'thumbnailUrl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder thumbnailUrlContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'thumbnailUrl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder thumbnailUrlMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'thumbnailUrl', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder thumbnailUrlIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'thumbnailUrl', + value: '', + )); + }); + } + + QueryBuilder + thumbnailUrlIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'thumbnailUrl', + value: '', + )); + }); + } + QueryBuilder updatedAtIsNull() { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(const FilterCondition.isNull( @@ -1479,6 +2163,42 @@ extension ChapterQuerySortBy on QueryBuilder { }); } + QueryBuilder sortByDescription() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'description', Sort.asc); + }); + } + + QueryBuilder sortByDescriptionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'description', Sort.desc); + }); + } + + QueryBuilder sortByDownloadSize() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'downloadSize', Sort.asc); + }); + } + + QueryBuilder sortByDownloadSizeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'downloadSize', Sort.desc); + }); + } + + QueryBuilder sortByDuration() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'duration', Sort.asc); + }); + } + + QueryBuilder sortByDurationDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'duration', Sort.desc); + }); + } + QueryBuilder sortByIsBookmarked() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'isBookmarked', Sort.asc); @@ -1491,6 +2211,18 @@ extension ChapterQuerySortBy on QueryBuilder { }); } + QueryBuilder sortByIsFiller() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFiller', Sort.asc); + }); + } + + QueryBuilder sortByIsFillerDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFiller', Sort.desc); + }); + } + QueryBuilder sortByIsRead() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'isRead', Sort.asc); @@ -1551,6 +2283,18 @@ extension ChapterQuerySortBy on QueryBuilder { }); } + QueryBuilder sortByThumbnailUrl() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'thumbnailUrl', Sort.asc); + }); + } + + QueryBuilder sortByThumbnailUrlDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'thumbnailUrl', Sort.desc); + }); + } + QueryBuilder sortByUpdatedAt() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'updatedAt', Sort.asc); @@ -1602,6 +2346,42 @@ extension ChapterQuerySortThenBy }); } + QueryBuilder thenByDescription() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'description', Sort.asc); + }); + } + + QueryBuilder thenByDescriptionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'description', Sort.desc); + }); + } + + QueryBuilder thenByDownloadSize() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'downloadSize', Sort.asc); + }); + } + + QueryBuilder thenByDownloadSizeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'downloadSize', Sort.desc); + }); + } + + QueryBuilder thenByDuration() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'duration', Sort.asc); + }); + } + + QueryBuilder thenByDurationDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'duration', Sort.desc); + }); + } + QueryBuilder thenById() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'id', Sort.asc); @@ -1626,6 +2406,18 @@ extension ChapterQuerySortThenBy }); } + QueryBuilder thenByIsFiller() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFiller', Sort.asc); + }); + } + + QueryBuilder thenByIsFillerDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFiller', Sort.desc); + }); + } + QueryBuilder thenByIsRead() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'isRead', Sort.asc); @@ -1686,6 +2478,18 @@ extension ChapterQuerySortThenBy }); } + QueryBuilder thenByThumbnailUrl() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'thumbnailUrl', Sort.asc); + }); + } + + QueryBuilder thenByThumbnailUrlDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'thumbnailUrl', Sort.desc); + }); + } + QueryBuilder thenByUpdatedAt() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'updatedAt', Sort.asc); @@ -1727,12 +2531,39 @@ extension ChapterQueryWhereDistinct }); } + QueryBuilder distinctByDescription( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'description', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByDownloadSize( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'downloadSize', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByDuration( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'duration', caseSensitive: caseSensitive); + }); + } + QueryBuilder distinctByIsBookmarked() { return QueryBuilder.apply(this, (query) { return query.addDistinctBy(r'isBookmarked'); }); } + QueryBuilder distinctByIsFiller() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isFiller'); + }); + } + QueryBuilder distinctByIsRead() { return QueryBuilder.apply(this, (query) { return query.addDistinctBy(r'isRead'); @@ -1766,6 +2597,13 @@ extension ChapterQueryWhereDistinct }); } + QueryBuilder distinctByThumbnailUrl( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'thumbnailUrl', caseSensitive: caseSensitive); + }); + } + QueryBuilder distinctByUpdatedAt() { return QueryBuilder.apply(this, (query) { return query.addDistinctBy(r'updatedAt'); @@ -1800,12 +2638,36 @@ extension ChapterQueryProperty }); } + QueryBuilder descriptionProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'description'); + }); + } + + QueryBuilder downloadSizeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'downloadSize'); + }); + } + + QueryBuilder durationProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'duration'); + }); + } + QueryBuilder isBookmarkedProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'isBookmarked'); }); } + QueryBuilder isFillerProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isFiller'); + }); + } + QueryBuilder isReadProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'isRead'); @@ -1836,6 +2698,12 @@ extension ChapterQueryProperty }); } + QueryBuilder thumbnailUrlProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'thumbnailUrl'); + }); + } + QueryBuilder updatedAtProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'updatedAt'); diff --git a/lib/models/settings.dart b/lib/models/settings.dart index bfd4a2ac..d9094209 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -258,6 +258,8 @@ class Settings { bool? rpcShowCoverImage; + bool? useAnime4K; + Settings({ this.id = 227, this.updatedAt = 0, @@ -373,6 +375,7 @@ class Settings { this.rpcShowReadingWatchingProgress = true, this.rpcShowTitle = true, this.rpcShowCoverImage = true, + this.useAnime4K = false, }); Settings.fromJson(Map json) { @@ -594,6 +597,7 @@ class Settings { rpcShowReadingWatchingProgress = json['rpcShowReadingWatchingProgress']; rpcShowTitle = json['rpcShowTitle']; rpcShowCoverImage = json['rpcShowCoverImage']; + useAnime4K = json['useAnime4K']; } Map toJson() => { @@ -732,6 +736,7 @@ class Settings { 'rpcShowReadingWatchingProgress': rpcShowReadingWatchingProgress, 'rpcShowTitle': rpcShowTitle, 'rpcShowCoverImage': rpcShowCoverImage, + 'useAnime4K': useAnime4K, }; } diff --git a/lib/modules/anime/anime_player_view.dart b/lib/modules/anime/anime_player_view.dart index c69190fe..da71e146 100644 --- a/lib/modules/anime/anime_player_view.dart +++ b/lib/modules/anime/anime_player_view.dart @@ -1,7 +1,10 @@ import 'dart:async'; +import 'dart:ffi'; import 'dart:io'; import 'package:bot_toast/bot_toast.dart'; +import 'package:ffi/ffi.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -31,6 +34,7 @@ import 'package:mangayomi/services/torrent_server.dart'; import 'package:mangayomi/utils/extensions/build_context_extensions.dart'; import 'package:mangayomi/utils/language.dart'; import 'package:media_kit/media_kit.dart'; +import 'package:media_kit/generated/libmpv/bindings.dart' as generated; import 'package:media_kit_video/media_kit_video.dart'; import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart'; import 'package:path/path.dart' as p; @@ -74,7 +78,7 @@ class _AnimePlayerViewState extends riv.ConsumerState { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); return serversData.when( data: (data) { - final (videos, isLocal, infoHashList) = data; + final (videos, isLocal, infoHashList, mpvDirectory) = data; _infoHashList = infoHashList; if (videos.isEmpty && !(episode.manga.value!.isLocalArchive ?? false)) { return Scaffold( @@ -102,6 +106,7 @@ class _AnimePlayerViewState extends riv.ConsumerState { desktopFullScreenPlayer: (value) { desktopFullScreenPlayer = value; }, + mpvDirectory: mpvDirectory, ); }, error: (error, stackTrace) => Scaffold( @@ -150,6 +155,7 @@ class AnimeStreamPage extends riv.ConsumerStatefulWidget { final String defaultSubtitle; final bool isLocal; final bool isTorrent; + final Directory? mpvDirectory; final void Function(bool) desktopFullScreenPlayer; const AnimeStreamPage({ super.key, @@ -159,6 +165,7 @@ class AnimeStreamPage extends riv.ConsumerStatefulWidget { required this.episode, required this.isTorrent, required this.desktopFullScreenPlayer, + required this.mpvDirectory, }); @override @@ -175,8 +182,15 @@ class _AnimeStreamPageState extends riv.ConsumerState with TickerProviderStateMixin, WidgetsBindingObserver { late final GlobalKey _key = GlobalKey(); late final useLibass = ref.read(useLibassStateProvider); + late final useAnime4K = ref.read(useAnime4KStateProvider); late final Player _player = Player( - configuration: PlayerConfiguration(libass: useLibass), + configuration: PlayerConfiguration( + libass: useLibass, + config: true, + configDir: useAnime4K ? widget.mpvDirectory?.path ?? "" : "", + observeProperties: {}, + eventHandler: _handleMpvEvents, + ), ); late final hwdecMode = ref.read(hwdecModeStateProvider()); late final VideoController _controller = VideoController( @@ -249,6 +263,25 @@ class _AnimeStreamPageState extends riv.ConsumerState } }); + Future _handleMpvEvents(Pointer event) async { + try { + if (event.ref.event_id == + generated.mpv_event_id.MPV_EVENT_PROPERTY_CHANGE) { + final prop = event.ref.data.cast(); + if (prop.ref.name.cast().toDartString() == + "user-data/aniyomi/dummy_number" && + prop.ref.format == generated.mpv_format.MPV_FORMAT_INT64) { + final number = prop.ref.data.cast().value; + botToast("Dummy number: $number"); + } + } + } catch (e) { + if (kDebugMode) { + debugPrint(e.toString()); + } + } + } + void pushToNewEpisode(BuildContext context, Chapter episode) { widget.desktopFullScreenPlayer.call(ref.read(fullscreenProvider)); if (context.mounted) { @@ -1033,6 +1066,34 @@ class _AnimeStreamPageState extends riv.ConsumerState onPressed: () => _videoSettingDraggableMenu(context), icon: const Icon(Icons.video_settings, color: Colors.white), ), + if (useAnime4K) + PopupMenuButton( + tooltip: '', // Remove default tooltip "Show menu" for consistency + icon: const Icon(Icons.high_quality, color: Colors.white), + itemBuilder: (context) => + [ + ("Anime4K: Mode A (Fast)", "CTRL+1"), + ("Anime4K: Mode B (Fast)", "CTRL+2"), + ("Anime4K: Mode C (Fast)", "CTRL+3"), + ("Anime4K: Mode A+A (Fast)", "CTRL+4"), + ("Anime4K: Mode B+B (Fast)", "CTRL+5"), + ("Anime4K: Mode C+A (Fast)", "CTRL+6"), + ("Clear GLSL shaders", "CTRL+0"), + ] + .map( + (mode) => PopupMenuItem( + value: mode.$1, + child: Text(mode.$1), + onTap: () { + (_player.platform as dynamic).command([ + "keydown", + mode.$2, + ]); + }, + ), + ) + .toList(), + ), PopupMenuButton( tooltip: '', // Remove default tooltip "Show menu" for consistency icon: const Icon(Icons.speed, color: Colors.white), diff --git a/lib/modules/anime/widgets/desktop.dart b/lib/modules/anime/widgets/desktop.dart index eadc18be..ae3a9032 100644 --- a/lib/modules/anime/widgets/desktop.dart +++ b/lib/modules/anime/widgets/desktop.dart @@ -215,6 +215,27 @@ class _DesktopControllerWidgetState final desktopFullScreenPlayer = widget.desktopFullScreenPlayer; await _changeFullScreen(ref, desktopFullScreenPlayer, value: false); }, + const SingleActivator(LogicalKeyboardKey.digit0, control: true): () { + (widget.videoController.player.platform as dynamic).command(["keydown", "CTRL+0"]); + }, + const SingleActivator(LogicalKeyboardKey.digit1, control: true): () { + (widget.videoController.player.platform as dynamic).command(["keydown", "CTRL+1"]); + }, + const SingleActivator(LogicalKeyboardKey.digit2, control: true): () { + (widget.videoController.player.platform as dynamic).command(["keydown", "CTRL+2"]); + }, + const SingleActivator(LogicalKeyboardKey.digit3, control: true): () { + (widget.videoController.player.platform as dynamic).command(["keydown", "CTRL+3"]); + }, + const SingleActivator(LogicalKeyboardKey.digit4, control: true): () { + (widget.videoController.player.platform as dynamic).command(["keydown", "CTRL+4"]); + }, + const SingleActivator(LogicalKeyboardKey.digit5, control: true): () { + (widget.videoController.player.platform as dynamic).command(["keydown", "CTRL+5"]); + }, + const SingleActivator(LogicalKeyboardKey.digit6, control: true): () { + (widget.videoController.player.platform as dynamic).command(["keydown", "CTRL+6"]); + }, }, child: Stack( children: [ diff --git a/lib/modules/manga/detail/providers/update_manga_detail_providers.dart b/lib/modules/manga/detail/providers/update_manga_detail_providers.dart index c5a16393..162f22a3 100644 --- a/lib/modules/manga/detail/providers/update_manga_detail_providers.dart +++ b/lib/modules/manga/detail/providers/update_manga_detail_providers.dart @@ -81,6 +81,11 @@ Future updateMangaDetail( scanlator: chaps[i].scanlator ?? '', mangaId: mangaId, updatedAt: DateTime.now().millisecondsSinceEpoch, + isFiller: chaps[i].isFiller, + thumbnailUrl: chaps[i].thumbnailUrl, + description: chaps[i].description, + downloadSize: chaps[i].downloadSize, + duration: chaps[i].duration, )..manga.value = manga; chapters.add(chapter); } @@ -115,6 +120,11 @@ Future updateMangaDetail( oldChap.url = newChap.url; oldChap.scanlator = newChap.scanlator; oldChap.updatedAt = DateTime.now().millisecondsSinceEpoch; + oldChap.isFiller = newChap.isFiller; + oldChap.thumbnailUrl = newChap.thumbnailUrl; + oldChap.description = newChap.description; + oldChap.downloadSize = newChap.downloadSize; + oldChap.duration = newChap.duration; isar.chapters.putSync(oldChap); oldChap.manga.saveSync(); } diff --git a/lib/modules/manga/detail/providers/update_manga_detail_providers.g.dart b/lib/modules/manga/detail/providers/update_manga_detail_providers.g.dart index 637e1db6..a818c90a 100644 --- a/lib/modules/manga/detail/providers/update_manga_detail_providers.g.dart +++ b/lib/modules/manga/detail/providers/update_manga_detail_providers.g.dart @@ -6,7 +6,7 @@ part of 'update_manga_detail_providers.dart'; // RiverpodGenerator // ************************************************************************** -String _$updateMangaDetailHash() => r'ce51918a48b315c3555b3de4e602bd998e00a992'; +String _$updateMangaDetailHash() => r'f75938777640ae0cfee181a2df7a12a56c42db41'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/modules/manga/reader/double_columm_view_center.dart b/lib/modules/manga/reader/double_columm_view_center.dart index bdc7de15..39d4eda8 100644 --- a/lib/modules/manga/reader/double_columm_view_center.dart +++ b/lib/modules/manga/reader/double_columm_view_center.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:mangayomi/main.dart'; import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/modules/manga/reader/image_view_paged.dart'; -import 'package:mangayomi/modules/manga/reader/reader_view.dart'; +import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; import 'package:mangayomi/modules/manga/reader/widgets/circular_progress_indicator_animate_rotate.dart'; import 'package:mangayomi/modules/manga/reader/widgets/transition_view_paged.dart'; import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart'; diff --git a/lib/modules/manga/reader/double_columm_view_vertical.dart b/lib/modules/manga/reader/double_columm_view_vertical.dart index 7d08bd72..cf01cbd1 100644 --- a/lib/modules/manga/reader/double_columm_view_vertical.dart +++ b/lib/modules/manga/reader/double_columm_view_vertical.dart @@ -2,7 +2,7 @@ import 'package:extended_image/extended_image.dart'; import 'package:flutter/material.dart'; import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/modules/manga/reader/image_view_paged.dart'; -import 'package:mangayomi/modules/manga/reader/reader_view.dart'; +import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; import 'package:mangayomi/modules/manga/reader/widgets/circular_progress_indicator_animate_rotate.dart'; import 'package:mangayomi/modules/manga/reader/widgets/transition_view_vertical.dart'; import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart'; diff --git a/lib/modules/manga/reader/image_view_paged.dart b/lib/modules/manga/reader/image_view_paged.dart index 82e828b4..3d765d42 100644 --- a/lib/modules/manga/reader/image_view_paged.dart +++ b/lib/modules/manga/reader/image_view_paged.dart @@ -2,7 +2,7 @@ import 'package:extended_image/extended_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provider.dart'; -import 'package:mangayomi/modules/manga/reader/reader_view.dart'; +import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; import 'package:mangayomi/modules/manga/reader/widgets/color_filter_widget.dart'; import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart'; import 'package:mangayomi/utils/extensions/others.dart'; diff --git a/lib/modules/manga/reader/image_view_vertical.dart b/lib/modules/manga/reader/image_view_vertical.dart index a7a95a16..b505826a 100644 --- a/lib/modules/manga/reader/image_view_vertical.dart +++ b/lib/modules/manga/reader/image_view_vertical.dart @@ -2,7 +2,7 @@ import 'package:extended_image/extended_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provider.dart'; -import 'package:mangayomi/modules/manga/reader/reader_view.dart'; +import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; import 'package:mangayomi/modules/manga/reader/widgets/color_filter_widget.dart'; import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart'; import 'package:mangayomi/providers/l10n_providers.dart'; diff --git a/lib/modules/manga/reader/providers/crop_borders_provider.dart b/lib/modules/manga/reader/providers/crop_borders_provider.dart index dad45c99..33c63d57 100644 --- a/lib/modules/manga/reader/providers/crop_borders_provider.dart +++ b/lib/modules/manga/reader/providers/crop_borders_provider.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:isolate'; import 'package:flutter/foundation.dart'; -import 'package:mangayomi/modules/manga/reader/reader_view.dart'; +import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; import 'package:mangayomi/src/rust/api/image.dart'; import 'package:mangayomi/src/rust/frb_generated.dart'; import 'package:mangayomi/utils/extensions/others.dart'; diff --git a/lib/modules/manga/reader/reader_view.dart b/lib/modules/manga/reader/reader_view.dart index 08a78c99..8c160dcf 100644 --- a/lib/modules/manga/reader/reader_view.dart +++ b/lib/modules/manga/reader/reader_view.dart @@ -12,14 +12,16 @@ import 'package:mangayomi/eval/model/m_bridge.dart'; import 'package:mangayomi/main.dart'; import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/models/manga.dart'; -import 'package:mangayomi/models/page.dart'; import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/modules/anime/widgets/desktop.dart'; import 'package:mangayomi/modules/manga/reader/providers/crop_borders_provider.dart'; +import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; import 'package:mangayomi/modules/manga/reader/widgets/btn_chapter_list_dialog.dart'; import 'package:mangayomi/modules/manga/reader/double_columm_view_center.dart'; import 'package:mangayomi/modules/manga/reader/providers/color_filter_provider.dart'; import 'package:mangayomi/modules/manga/reader/widgets/color_filter_widget.dart'; +import 'package:mangayomi/modules/manga/reader/widgets/custom_popup_menu_button.dart'; +import 'package:mangayomi/modules/manga/reader/widgets/custom_value_indicator_shape.dart'; import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart'; import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart'; import 'package:mangayomi/providers/l10n_providers.dart'; @@ -1633,7 +1635,7 @@ class _MangaChapterPageGalleryState return SliderTheme( data: SliderTheme.of(context).copyWith( valueIndicatorShape: - _CustomValueIndicatorShape( + CustomValueIndicatorShape( tranform: _isReverseHorizontal, ), overlayShape: @@ -2504,190 +2506,3 @@ class _MangaChapterPageGalleryState } } } - -class UChapDataPreload { - Chapter? chapter; - Directory? directory; - PageUrl? pageUrl; - bool? isLocale; - Uint8List? archiveImage; - int? index; - GetChapterPagesModel? chapterUrlModel; - int? pageIndex; - Uint8List? cropImage; - bool isTransitionPage; - Chapter? nextChapter; - String? mangaName; - bool? isLastChapter; - - UChapDataPreload( - this.chapter, - this.directory, - this.pageUrl, - this.isLocale, - this.archiveImage, - this.index, - this.chapterUrlModel, - this.pageIndex, { - this.cropImage, - this.isTransitionPage = false, - this.nextChapter, - this.mangaName, - this.isLastChapter = false, - }); - - UChapDataPreload.transition({ - required Chapter currentChapter, - required this.nextChapter, - required String this.mangaName, - required int this.pageIndex, - this.isLastChapter = false, - }) : chapter = currentChapter, - isTransitionPage = true, - directory = null, - pageUrl = null, - isLocale = null, - archiveImage = null, - index = null, - chapterUrlModel = null, - cropImage = null; -} - -class CustomPopupMenuButton extends StatelessWidget { - final String label; - final String title; - final ValueChanged onSelected; - final T value; - final List list; - final String Function(T) itemText; - const CustomPopupMenuButton({ - super.key, - required this.label, - required this.title, - required this.onSelected, - required this.value, - required this.list, - required this.itemText, - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: PopupMenuButton( - popUpAnimationStyle: popupAnimationStyle, - tooltip: "", - offset: Offset.fromDirection(1), - color: Colors.black, - onSelected: onSelected, - itemBuilder: (context) => [ - for (var d in list) - PopupMenuItem( - value: d, - child: Row( - children: [ - Icon( - Icons.check, - color: d == value ? Colors.white : Colors.transparent, - ), - const SizedBox(width: 7), - Text( - itemText(d), - style: const TextStyle(color: Colors.white), - ), - ], - ), - ), - ], - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Text( - label, - style: TextStyle( - color: Theme.of( - context, - ).textTheme.bodyLarge!.color!.withValues(alpha: 0.9), - ), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), - Row( - children: [ - Text(title), - const SizedBox(width: 20), - const Icon(Icons.keyboard_arrow_down_outlined), - ], - ), - ], - ), - ), - ), - ); - } -} - -class _CustomValueIndicatorShape extends SliderComponentShape { - final _indicatorShape = const PaddleSliderValueIndicatorShape(); - final bool tranform; - const _CustomValueIndicatorShape({this.tranform = false}); - @override - Size getPreferredSize(bool isEnabled, bool isDiscrete) { - return const Size(40, 40); - } - - @override - void paint( - PaintingContext context, - Offset center, { - required Animation activationAnimation, - required Animation enableAnimation, - required bool isDiscrete, - required TextPainter labelPainter, - required RenderBox parentBox, - required SliderThemeData sliderTheme, - required TextDirection textDirection, - required double value, - required double textScaleFactor, - required Size sizeWithOverflow, - }) { - final textSpan = TextSpan( - text: labelPainter.text?.toPlainText(), - style: sliderTheme.valueIndicatorTextStyle, - ); - - final textPainter = TextPainter( - text: textSpan, - textAlign: labelPainter.textAlign, - textDirection: textDirection, - ); - - textPainter.layout(); - - context.canvas.save(); - context.canvas.translate(center.dx, center.dy); - context.canvas.scale(tranform ? -1.0 : 1.0, 1.0); - context.canvas.translate(-center.dx, -center.dy); - - _indicatorShape.paint( - context, - center, - activationAnimation: activationAnimation, - enableAnimation: enableAnimation, - labelPainter: textPainter, - parentBox: parentBox, - sliderTheme: sliderTheme, - value: value, - textScaleFactor: textScaleFactor, - sizeWithOverflow: sizeWithOverflow, - isDiscrete: isDiscrete, - textDirection: textDirection, - ); - - context.canvas.restore(); - } -} diff --git a/lib/modules/manga/reader/u_chap_data_preload.dart b/lib/modules/manga/reader/u_chap_data_preload.dart new file mode 100644 index 00000000..68b7f1f4 --- /dev/null +++ b/lib/modules/manga/reader/u_chap_data_preload.dart @@ -0,0 +1,54 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:mangayomi/models/chapter.dart'; +import 'package:mangayomi/models/page.dart'; +import 'package:mangayomi/services/get_chapter_pages.dart'; + +class UChapDataPreload { + Chapter? chapter; + Directory? directory; + PageUrl? pageUrl; + bool? isLocale; + Uint8List? archiveImage; + int? index; + GetChapterPagesModel? chapterUrlModel; + int? pageIndex; + Uint8List? cropImage; + bool isTransitionPage; + Chapter? nextChapter; + String? mangaName; + bool? isLastChapter; + + UChapDataPreload( + this.chapter, + this.directory, + this.pageUrl, + this.isLocale, + this.archiveImage, + this.index, + this.chapterUrlModel, + this.pageIndex, { + this.cropImage, + this.isTransitionPage = false, + this.nextChapter, + this.mangaName, + this.isLastChapter = false, + }); + + UChapDataPreload.transition({ + required Chapter currentChapter, + required this.nextChapter, + required String this.mangaName, + required int this.pageIndex, + this.isLastChapter = false, + }) : chapter = currentChapter, + isTransitionPage = true, + directory = null, + pageUrl = null, + isLocale = null, + archiveImage = null, + index = null, + chapterUrlModel = null, + cropImage = null; +} diff --git a/lib/modules/manga/reader/virtual_scrolling/virtual_manga_list.dart b/lib/modules/manga/reader/virtual_scrolling/virtual_manga_list.dart index 22274bcc..43bfd4dd 100644 --- a/lib/modules/manga/reader/virtual_scrolling/virtual_manga_list.dart +++ b/lib/modules/manga/reader/virtual_scrolling/virtual_manga_list.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/modules/manga/reader/virtual_scrolling/virtual_page_manager.dart'; -import 'package:mangayomi/modules/manga/reader/reader_view.dart' as reader; import 'package:mangayomi/modules/manga/reader/image_view_vertical.dart'; import 'package:mangayomi/modules/manga/reader/double_columm_view_vertical.dart'; import 'package:mangayomi/modules/manga/reader/widgets/transition_view_vertical.dart'; @@ -21,7 +21,7 @@ class VirtualMangaList extends ConsumerStatefulWidget { final double minCacheExtent; final int initialScrollIndex; final ScrollPhysics physics; - final Function(reader.UChapDataPreload data) onLongPressData; + final Function(UChapDataPreload data) onLongPressData; final Function(bool) onFailedToLoadImage; final BackgroundColor backgroundColor; final bool isDoublePageMode; @@ -215,7 +215,7 @@ class _VirtualMangaListState extends ConsumerState { final int index1 = index * 2 - 1; final int index2 = index1 + 1; - final List datas = index == 0 + final List datas = index == 0 ? [widget.pageManager.getOriginalPage(0), null] : [ index1 < widget.pageManager.pageCount diff --git a/lib/modules/manga/reader/virtual_scrolling/virtual_page_manager.dart b/lib/modules/manga/reader/virtual_scrolling/virtual_page_manager.dart index c954eeb8..38ce27b5 100644 --- a/lib/modules/manga/reader/virtual_scrolling/virtual_page_manager.dart +++ b/lib/modules/manga/reader/virtual_scrolling/virtual_page_manager.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:mangayomi/modules/manga/reader/reader_view.dart' as reader; +import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; /// Page loading states for virtual scrolling enum PageLoadState { notLoaded, loading, loaded, error, cached } @@ -10,7 +10,7 @@ enum PageLoadState { notLoaded, loading, loaded, error, cached } /// Virtual page information for tracking state class VirtualPageInfo { final int index; - final reader.UChapDataPreload originalData; + final UChapDataPreload originalData; PageLoadState loadState; DateTime? lastAccessTime; Object? error; @@ -56,7 +56,7 @@ class VirtualPageConfig { /// Manages virtual page loading and memory optimization class VirtualPageManager extends ChangeNotifier { - final List _originalPages; + final List _originalPages; final VirtualPageConfig config; final Map _pageInfoMap = {}; final Set _preloadQueue = {}; @@ -65,7 +65,7 @@ class VirtualPageManager extends ChangeNotifier { Timer? _cleanupTimer; VirtualPageManager({ - required List pages, + required List pages, this.config = const VirtualPageConfig(), }) : _originalPages = List.from(pages) { _initializePages(); @@ -108,7 +108,7 @@ class VirtualPageManager extends ChangeNotifier { } /// Get original page data - reader.UChapDataPreload? getOriginalPage(int index) { + UChapDataPreload? getOriginalPage(int index) { if (index < 0 || index >= _originalPages.length) return null; return _originalPages[index]; } diff --git a/lib/modules/manga/reader/virtual_scrolling/virtual_reader_view.dart b/lib/modules/manga/reader/virtual_scrolling/virtual_reader_view.dart index 136a90b6..b1aa41de 100644 --- a/lib/modules/manga/reader/virtual_scrolling/virtual_reader_view.dart +++ b/lib/modules/manga/reader/virtual_scrolling/virtual_reader_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view_gallery.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; @@ -7,11 +8,10 @@ import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/modules/manga/reader/virtual_scrolling/virtual_page_manager.dart'; import 'package:mangayomi/modules/manga/reader/virtual_scrolling/virtual_manga_list.dart'; -import 'package:mangayomi/modules/manga/reader/reader_view.dart' as reader; /// Provides virtual page manager instances final virtualPageManagerProvider = - Provider.family>(( + Provider.family>(( ref, pages, ) { @@ -20,7 +20,7 @@ final virtualPageManagerProvider = /// Main widget for virtual reading that replaces ScrollablePositionedList class VirtualReaderView extends ConsumerStatefulWidget { - final List pages; + final List pages; final ItemScrollController itemScrollController; final ScrollOffsetController scrollOffsetController; final ItemPositionsListener itemPositionsListener; @@ -28,7 +28,7 @@ class VirtualReaderView extends ConsumerStatefulWidget { final double minCacheExtent; final int initialScrollIndex; final ScrollPhysics physics; - final Function(reader.UChapDataPreload data) onLongPressData; + final Function(UChapDataPreload data) onLongPressData; final Function(bool) onFailedToLoadImage; final BackgroundColor backgroundColor; final bool isDoublePageMode; @@ -169,10 +169,10 @@ mixin VirtualPageManagerMixin } /// Override this method to provide the pages list - List getPages(); + List getPages(); /// Call this when pages change - void updateVirtualPages(List newPages) { + void updateVirtualPages(List newPages) { _virtualPageManager?.dispose(); _virtualPageManager = VirtualPageManager(pages: newPages); } diff --git a/lib/modules/manga/reader/widgets/custom_popup_menu_button.dart b/lib/modules/manga/reader/widgets/custom_popup_menu_button.dart new file mode 100644 index 00000000..4f29e069 --- /dev/null +++ b/lib/modules/manga/reader/widgets/custom_popup_menu_button.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:mangayomi/utils/global_style.dart'; + +class CustomPopupMenuButton extends StatelessWidget { + final String label; + final String title; + final ValueChanged onSelected; + final T value; + final List list; + final String Function(T) itemText; + const CustomPopupMenuButton({ + super.key, + required this.label, + required this.title, + required this.onSelected, + required this.value, + required this.list, + required this.itemText, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: PopupMenuButton( + popUpAnimationStyle: popupAnimationStyle, + tooltip: "", + offset: Offset.fromDirection(1), + color: Colors.black, + onSelected: onSelected, + itemBuilder: (context) => [ + for (var d in list) + PopupMenuItem( + value: d, + child: Row( + children: [ + Icon( + Icons.check, + color: d == value ? Colors.white : Colors.transparent, + ), + const SizedBox(width: 7), + Text( + itemText(d), + style: const TextStyle(color: Colors.white), + ), + ], + ), + ), + ], + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + label, + style: TextStyle( + color: Theme.of( + context, + ).textTheme.bodyLarge!.color!.withValues(alpha: 0.9), + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + Row( + children: [ + Text(title), + const SizedBox(width: 20), + const Icon(Icons.keyboard_arrow_down_outlined), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/modules/manga/reader/widgets/custom_value_indicator_shape.dart b/lib/modules/manga/reader/widgets/custom_value_indicator_shape.dart new file mode 100644 index 00000000..ddda1f93 --- /dev/null +++ b/lib/modules/manga/reader/widgets/custom_value_indicator_shape.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; + +class CustomValueIndicatorShape extends SliderComponentShape { + final _indicatorShape = const PaddleSliderValueIndicatorShape(); + final bool tranform; + const CustomValueIndicatorShape({this.tranform = false}); + @override + Size getPreferredSize(bool isEnabled, bool isDiscrete) { + return const Size(40, 40); + } + + @override + void paint( + PaintingContext context, + Offset center, { + required Animation activationAnimation, + required Animation enableAnimation, + required bool isDiscrete, + required TextPainter labelPainter, + required RenderBox parentBox, + required SliderThemeData sliderTheme, + required TextDirection textDirection, + required double value, + required double textScaleFactor, + required Size sizeWithOverflow, + }) { + final textSpan = TextSpan( + text: labelPainter.text?.toPlainText(), + style: sliderTheme.valueIndicatorTextStyle, + ); + + final textPainter = TextPainter( + text: textSpan, + textAlign: labelPainter.textAlign, + textDirection: textDirection, + ); + + textPainter.layout(); + + context.canvas.save(); + context.canvas.translate(center.dx, center.dy); + context.canvas.scale(tranform ? -1.0 : 1.0, 1.0); + context.canvas.translate(-center.dx, -center.dy); + + _indicatorShape.paint( + context, + center, + activationAnimation: activationAnimation, + enableAnimation: enableAnimation, + labelPainter: textPainter, + parentBox: parentBox, + sliderTheme: sliderTheme, + value: value, + textScaleFactor: textScaleFactor, + sizeWithOverflow: sizeWithOverflow, + isDiscrete: isDiscrete, + textDirection: textDirection, + ); + + context.canvas.restore(); + } +} diff --git a/lib/modules/manga/reader/widgets/transition_view_paged.dart b/lib/modules/manga/reader/widgets/transition_view_paged.dart index 96abcd4d..57f8bb75 100644 --- a/lib/modules/manga/reader/widgets/transition_view_paged.dart +++ b/lib/modules/manga/reader/widgets/transition_view_paged.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mangayomi/modules/manga/reader/reader_view.dart'; +import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; import 'package:mangayomi/modules/manga/reader/widgets/chapter_transition_page.dart'; class TransitionViewPaged extends ConsumerWidget { diff --git a/lib/modules/manga/reader/widgets/transition_view_vertical.dart b/lib/modules/manga/reader/widgets/transition_view_vertical.dart index 2ea9f674..2a80e717 100644 --- a/lib/modules/manga/reader/widgets/transition_view_vertical.dart +++ b/lib/modules/manga/reader/widgets/transition_view_vertical.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mangayomi/modules/manga/reader/reader_view.dart'; +import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; import 'package:mangayomi/modules/manga/reader/widgets/chapter_transition_page.dart'; import 'package:mangayomi/utils/extensions/build_context_extensions.dart'; diff --git a/lib/modules/more/settings/player/player_screen.dart b/lib/modules/more/settings/player/player_screen.dart index e9407515..98d8368c 100644 --- a/lib/modules/more/settings/player/player_screen.dart +++ b/lib/modules/more/settings/player/player_screen.dart @@ -1,18 +1,43 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:archive/archive.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart'; import 'package:mangayomi/providers/l10n_providers.dart'; +import 'package:mangayomi/providers/storage_provider.dart'; import 'package:mangayomi/utils/extensions/build_context_extensions.dart'; import 'package:mangayomi/utils/language.dart'; import 'package:numberpicker/numberpicker.dart'; +import 'package:path/path.dart' as path; +import 'package:permission_handler/permission_handler.dart'; import 'package:super_sliver_list/super_sliver_list.dart'; import 'package:mangayomi/l10n/generated/app_localizations.dart'; -class PlayerScreen extends ConsumerWidget { +class PlayerScreen extends ConsumerStatefulWidget { const PlayerScreen({super.key}); @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => _PlayerScreenState(); +} + +class _PlayerScreenState extends ConsumerState { + int _total = 0; + int _received = 0; + late http.StreamedResponse _response; + final List _bytes = []; + late StreamSubscription>? _subscription; + + @override + void dispose() { + _subscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { final defaultSubtitleLang = ref.watch(defaultSubtitleLangStateProvider); final markEpisodeAsSeenType = ref.watch(markEpisodeAsSeenTypeStateProvider); final defaultSkipIntroLength = ref.watch( @@ -26,6 +51,7 @@ class PlayerScreen extends ConsumerWidget { final enableAutoSkip = ref.watch(enableAutoSkipStateProvider); final aniSkipTimeoutLength = ref.watch(aniSkipTimeoutLengthStateProvider); final useLibass = ref.watch(useLibassStateProvider); + final useAnime4K = ref.watch(useAnime4KStateProvider); final hwdecMode = ref.watch(hwdecModeStateProvider(rawValue: true)); final fullScreenPlayer = ref.watch(fullScreenPlayerStateProvider); @@ -457,6 +483,20 @@ class PlayerScreen extends ConsumerWidget { ), ], ), + SwitchListTile( + value: useAnime4K, + title: Text(context.l10n.anime4K), + subtitle: Text( + context.l10n.anime4K_info, + style: TextStyle(fontSize: 11, color: context.secondaryColor), + ), + onChanged: (value) async { + if (value && !(await _checkAnime4K(context))) { + return; + } + ref.read(useAnime4KStateProvider.notifier).set(value); + }, + ), SwitchListTile( value: fullScreenPlayer, title: Text(context.l10n.full_screen_player), @@ -501,7 +541,11 @@ class PlayerScreen extends ConsumerWidget { groupValue: hwdecMode, onChanged: (value) { ref - .read(hwdecModeStateProvider(rawValue: true).notifier) + .read( + hwdecModeStateProvider( + rawValue: true, + ).notifier, + ) .set(value!); Navigator.pop(context); }, @@ -547,4 +591,130 @@ class PlayerScreen extends ConsumerWidget { ), ); } + + Future _checkAnime4K(BuildContext context) async { + var status = await Permission.storage.status; + if (!status.isGranted) { + await Permission.storage.request(); + } + final provider = StorageProvider(); + final dir = await provider.getMpvDirectory(); + final mpvFile = File('${dir!.path}/mpv.conf'); + final inputFile = File('${dir.path}/input.conf'); + if (!(await mpvFile.exists()) && + !(await inputFile.exists()) && + context.mounted) { + final res = await showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: SingleChildScrollView( + child: Column( + children: [ + Text(context.l10n.anime4K_download), + _total > 0 + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Flexible( + child: LinearProgressIndicator( + value: _total > 0 + ? (_received * 1.0) / _total + : 0.0, + ), + ), + Flexible( + child: Text( + '${(_received / 1048576.0).toStringAsFixed(2)}/${(_total / 1048576.0).toStringAsFixed(2)} MB', + ), + ), + ], + ) + : SizedBox.shrink(), + ], + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () async { + try { + await _subscription?.cancel(); + } catch (_) {} + if (context.mounted) { + Navigator.pop(context); + } + }, + child: Text(context.l10n.cancel), + ), + const SizedBox(width: 15), + ElevatedButton( + onPressed: _total == 0 + ? () async { + _response = await http.Client().send( + http.Request( + 'GET', + Uri.parse( + "https://github.com/Tama47/Anime4K/releases/download/v4.0.1/GLSL_Windows_Low-end.zip", + ), + ), + ); + _total = _response.contentLength ?? 0; + _subscription = _response.stream.listen((value) { + setState(() { + _bytes.addAll(value); + _received += value.length; + }); + }); + _subscription?.onDone(() async { + final archive = ZipDecoder().decodeBytes(_bytes); + String shadersDir = path.join( + dir.path, + 'shaders', + ); + await Directory( + shadersDir, + ).create(recursive: true); + String scriptsDir = path.join( + dir.path, + 'scripts', + ); + await Directory( + scriptsDir, + ).create(recursive: true); + for (final file in archive.files) { + if (file.name == "mpv.conf") { + await mpvFile.writeAsBytes(file.content); + } else if (file.name == "input.conf") { + await inputFile.writeAsBytes(file.content); + } else if (file.name.endsWith(".glsl")) { + final shaderFile = File( + '$shadersDir/${file.name.split("/").last}', + ); + await shaderFile.writeAsBytes(file.content); + } + } + _total = 0; + _received = 0; + _bytes.clear(); + if (context.mounted) { + Navigator.pop(context, "ok"); + } + }); + } + : null, + child: Text(context.l10n.download), + ), + ], + ), + ], + ); + }, + ); + return res != null && res == "ok"; + } + return context.mounted; + } } diff --git a/lib/modules/more/settings/player/providers/player_state_provider.dart b/lib/modules/more/settings/player/providers/player_state_provider.dart index 55f1ca48..d5c54e45 100644 --- a/lib/modules/more/settings/player/providers/player_state_provider.dart +++ b/lib/modules/more/settings/player/providers/player_state_provider.dart @@ -218,6 +218,22 @@ class UseLibassState extends _$UseLibassState { } } +@riverpod +class UseAnime4KState extends _$UseAnime4KState { + @override + bool build() { + return isar.settings.getSync(227)!.useAnime4K ?? false; + } + + void set(bool value) { + final settings = isar.settings.getSync(227); + state = value; + isar.writeTxnSync( + () => isar.settings.putSync(settings!..useAnime4K = value), + ); + } +} + final hwdecs = { "no": ["all"], "auto": ["all"], diff --git a/lib/modules/novel/novel_reader_view.dart b/lib/modules/novel/novel_reader_view.dart index 4d9df59d..2a54371d 100644 --- a/lib/modules/novel/novel_reader_view.dart +++ b/lib/modules/novel/novel_reader_view.dart @@ -11,7 +11,6 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mangayomi/main.dart'; import 'package:mangayomi/models/chapter.dart'; -import 'package:mangayomi/models/page.dart'; import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/modules/anime/widgets/desktop.dart'; import 'package:mangayomi/modules/manga/reader/widgets/btn_chapter_list_dialog.dart'; @@ -22,9 +21,7 @@ import 'package:mangayomi/services/get_html_content.dart'; import 'package:mangayomi/utils/extensions/dom_extensions.dart'; import 'package:mangayomi/utils/utils.dart'; import 'package:mangayomi/modules/manga/reader/providers/push_router.dart'; -import 'package:mangayomi/services/get_chapter_pages.dart'; import 'package:mangayomi/utils/extensions/build_context_extensions.dart'; -import 'package:mangayomi/utils/global_style.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:window_manager/window_manager.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; @@ -759,104 +756,3 @@ class _NovelWebViewState extends ConsumerState return null; } } - -class UChapDataPreload { - Chapter? chapter; - Directory? directory; - PageUrl? pageUrl; - bool? isLocale; - Uint8List? archiveImage; - int? index; - GetChapterPagesModel? chapterUrlModel; - int? pageIndex; - Uint8List? cropImage; - UChapDataPreload( - this.chapter, - this.directory, - this.pageUrl, - this.isLocale, - this.archiveImage, - this.index, - this.chapterUrlModel, - this.pageIndex, { - this.cropImage, - }); -} - -class CustomPopupMenuButton extends StatelessWidget { - final String label; - final String title; - final ValueChanged onSelected; - final T value; - final List list; - final String Function(T) itemText; - const CustomPopupMenuButton({ - super.key, - required this.label, - required this.title, - required this.onSelected, - required this.value, - required this.list, - required this.itemText, - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: PopupMenuButton( - popUpAnimationStyle: popupAnimationStyle, - tooltip: "", - offset: Offset.fromDirection(1), - color: Colors.black, - onSelected: onSelected, - itemBuilder: (context) => [ - for (var d in list) - PopupMenuItem( - value: d, - child: Row( - children: [ - Icon( - Icons.check, - color: d == value ? Colors.white : Colors.transparent, - ), - const SizedBox(width: 7), - Text( - itemText(d), - style: const TextStyle(color: Colors.white), - ), - ], - ), - ), - ], - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Text( - label, - style: TextStyle( - color: Theme.of( - context, - ).textTheme.bodyLarge!.color!.withValues(alpha: 0.9), - ), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), - Row( - children: [ - Text(title), - const SizedBox(width: 20), - const Icon(Icons.keyboard_arrow_down_outlined), - ], - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/providers/storage_provider.dart b/lib/providers/storage_provider.dart index f7b2dfe6..cc11ce4c 100644 --- a/lib/providers/storage_provider.dart +++ b/lib/providers/storage_provider.dart @@ -61,6 +61,13 @@ class StorageProvider { return directory; } + Future getMpvDirectory() async { + final defaultDirectory = await getDefaultDirectory(); + String dbDir = path.join(defaultDirectory!.path, 'mpv'); + await Directory(dbDir).create(recursive: true); + return Directory(dbDir); + } + Future getBtDirectory() async { final gefaultDirectory = await getDefaultDirectory(); String dbDir = path.join(gefaultDirectory!.path, 'torrents'); diff --git a/lib/services/get_chapter_pages.dart b/lib/services/get_chapter_pages.dart index d073a765..81d099b2 100644 --- a/lib/services/get_chapter_pages.dart +++ b/lib/services/get_chapter_pages.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; +import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; import 'package:path/path.dart' as p; import 'package:mangayomi/eval/lib.dart'; import 'package:mangayomi/eval/javascript/http.dart'; @@ -9,7 +10,6 @@ import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/models/page.dart'; import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/modules/manga/archive_reader/providers/archive_reader_providers.dart'; -import 'package:mangayomi/modules/manga/reader/reader_view.dart'; import 'package:mangayomi/providers/storage_provider.dart'; import 'package:mangayomi/utils/utils.dart'; import 'package:mangayomi/utils/reg_exp_matcher.dart'; diff --git a/lib/services/get_video_list.dart b/lib/services/get_video_list.dart index d1bf5984..b7cbf195 100644 --- a/lib/services/get_video_list.dart +++ b/lib/services/get_video_list.dart @@ -13,11 +13,12 @@ import 'package:path/path.dart' as p; part 'get_video_list.g.dart'; @riverpod -Future<(List