Merge branch 'temp/mpv-anime4k' into feature/mpv-anime4k

This commit is contained in:
Schnitzel5 2025-07-24 22:31:58 +02:00
commit 38728ce0cb
49 changed files with 1690 additions and 382 deletions

View file

@ -12,6 +12,11 @@ class MChapterBridge {
url: namedArgs.get<String?>('url'),
dateUpload: namedArgs.get<String?>('dateUpload'),
scanlator: namedArgs.get<String?>('scanlator'),
isFiller: namedArgs.get<bool?>('isFiller'),
thumbnailUrl: namedArgs.get<String?>('scanlator'),
description: namedArgs.get<String?>('scanlator'),
downloadSize: namedArgs.get<String?>('scanlator'),
duration: namedArgs.get<String?>('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) {

View file

@ -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<String, dynamic> 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<String, dynamic> toJson() => {
@ -20,5 +48,10 @@ class MChapter {
'url': url,
'dateUpload': dateUpload,
'scanlator': scanlator,
'isFiller': isFiller,
'thumbnailUrl': thumbnailUrl,
'description': description,
'downloadSize': downloadSize,
'duration': duration,
};
}

View file

@ -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?"
}

View file

@ -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

View file

@ -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?';
}

View file

@ -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?';
}

View file

@ -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?';
}

View file

@ -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`).

View file

@ -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?';
}

View file

@ -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?';
}

View file

@ -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?';
}

View file

@ -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`).

View file

@ -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?';
}

View file

@ -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?';
}

View file

@ -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?';
}

View file

@ -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?';
}

View file

@ -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';

View file

@ -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<Manga>();
@ -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,
};
}

File diff suppressed because it is too large Load diff

View file

@ -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<String, dynamic> json) {
@ -594,6 +597,7 @@ class Settings {
rpcShowReadingWatchingProgress = json['rpcShowReadingWatchingProgress'];
rpcShowTitle = json['rpcShowTitle'];
rpcShowCoverImage = json['rpcShowCoverImage'];
useAnime4K = json['useAnime4K'];
}
Map<String, dynamic> toJson() => {
@ -732,6 +736,7 @@ class Settings {
'rpcShowReadingWatchingProgress': rpcShowReadingWatchingProgress,
'rpcShowTitle': rpcShowTitle,
'rpcShowCoverImage': rpcShowCoverImage,
'useAnime4K': useAnime4K,
};
}

View file

@ -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<AnimePlayerView> {
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<AnimePlayerView> {
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<AnimeStreamPage>
with TickerProviderStateMixin, WidgetsBindingObserver {
late final GlobalKey<VideoState> _key = GlobalKey<VideoState>();
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<AnimeStreamPage>
}
});
Future<void> _handleMpvEvents(Pointer<generated.mpv_event> event) async {
try {
if (event.ref.event_id ==
generated.mpv_event_id.MPV_EVENT_PROPERTY_CHANGE) {
final prop = event.ref.data.cast<generated.mpv_event_property>();
if (prop.ref.name.cast<Utf8>().toDartString() ==
"user-data/aniyomi/dummy_number" &&
prop.ref.format == generated.mpv_format.MPV_FORMAT_INT64) {
final number = prop.ref.data.cast<Int64>().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<AnimeStreamPage>
onPressed: () => _videoSettingDraggableMenu(context),
icon: const Icon(Icons.video_settings, color: Colors.white),
),
if (useAnime4K)
PopupMenuButton<String>(
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<String>(
value: mode.$1,
child: Text(mode.$1),
onTap: () {
(_player.platform as dynamic).command([
"keydown",
mode.$2,
]);
},
),
)
.toList(),
),
PopupMenuButton<double>(
tooltip: '', // Remove default tooltip "Show menu" for consistency
icon: const Icon(Icons.speed, color: Colors.white),

View file

@ -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: [

View file

@ -81,6 +81,11 @@ Future<dynamic> 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<dynamic> 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();
}

View file

@ -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 {

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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<T> extends StatelessWidget {
final String label;
final String title;
final ValueChanged<T> onSelected;
final T value;
final List<T> 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<double> activationAnimation,
required Animation<double> 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();
}
}

View file

@ -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;
}

View file

@ -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<VirtualMangaList> {
final int index1 = index * 2 - 1;
final int index2 = index1 + 1;
final List<reader.UChapDataPreload?> datas = index == 0
final List<UChapDataPreload?> datas = index == 0
? [widget.pageManager.getOriginalPage(0), null]
: [
index1 < widget.pageManager.pageCount

View file

@ -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<reader.UChapDataPreload> _originalPages;
final List<UChapDataPreload> _originalPages;
final VirtualPageConfig config;
final Map<int, VirtualPageInfo> _pageInfoMap = {};
final Set<int> _preloadQueue = {};
@ -65,7 +65,7 @@ class VirtualPageManager extends ChangeNotifier {
Timer? _cleanupTimer;
VirtualPageManager({
required List<reader.UChapDataPreload> pages,
required List<UChapDataPreload> 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];
}

View file

@ -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<VirtualPageManager, List<reader.UChapDataPreload>>((
Provider.family<VirtualPageManager, List<UChapDataPreload>>((
ref,
pages,
) {
@ -20,7 +20,7 @@ final virtualPageManagerProvider =
/// Main widget for virtual reading that replaces ScrollablePositionedList
class VirtualReaderView extends ConsumerStatefulWidget {
final List<reader.UChapDataPreload> pages;
final List<UChapDataPreload> 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<T extends ConsumerStatefulWidget>
}
/// Override this method to provide the pages list
List<reader.UChapDataPreload> getPages();
List<UChapDataPreload> getPages();
/// Call this when pages change
void updateVirtualPages(List<reader.UChapDataPreload> newPages) {
void updateVirtualPages(List<UChapDataPreload> newPages) {
_virtualPageManager?.dispose();
_virtualPageManager = VirtualPageManager(pages: newPages);
}

View file

@ -0,0 +1,80 @@
import 'package:flutter/material.dart';
import 'package:mangayomi/utils/global_style.dart';
class CustomPopupMenuButton<T> extends StatelessWidget {
final String label;
final String title;
final ValueChanged<T> onSelected;
final T value;
final List<T> 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),
],
),
],
),
),
),
);
}
}

View file

@ -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<double> activationAnimation,
required Animation<double> 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();
}
}

View file

@ -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 {

View file

@ -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';

View file

@ -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<PlayerScreen> createState() => _PlayerScreenState();
}
class _PlayerScreenState extends ConsumerState<PlayerScreen> {
int _total = 0;
int _received = 0;
late http.StreamedResponse _response;
final List<int> _bytes = [];
late StreamSubscription<List<int>>? _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<bool> _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;
}
}

View file

@ -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"],

View file

@ -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<NovelWebView>
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<T> extends StatelessWidget {
final String label;
final String title;
final ValueChanged<T> onSelected;
final T value;
final List<T> 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),
],
),
],
),
),
),
);
}
}

View file

@ -61,6 +61,13 @@ class StorageProvider {
return directory;
}
Future<Directory?> getMpvDirectory() async {
final defaultDirectory = await getDefaultDirectory();
String dbDir = path.join(defaultDirectory!.path, 'mpv');
await Directory(dbDir).create(recursive: true);
return Directory(dbDir);
}
Future<Directory?> getBtDirectory() async {
final gefaultDirectory = await getDefaultDirectory();
String dbDir = path.join(gefaultDirectory!.path, 'torrents');

View file

@ -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';

View file

@ -13,11 +13,12 @@ import 'package:path/path.dart' as p;
part 'get_video_list.g.dart';
@riverpod
Future<(List<Video>, bool, List<String>)> getVideoList(
Future<(List<Video>, bool, List<String>, Directory?)> getVideoList(
Ref ref, {
required Chapter episode,
}) async {
final storageProvider = StorageProvider();
final mpvDirectory = await storageProvider.getMpvDirectory();
final mangaDirectory = await storageProvider.getMangaMainDirectory(episode);
final isLocalArchive =
episode.manga.value!.isLocalArchive! &&
@ -52,6 +53,7 @@ Future<(List<Video>, bool, List<String>)> getVideoList(
[Video(path!, episode.name!, path, subtitles: subtitles)],
true,
infoHashes,
mpvDirectory
);
}
final source = getSource(
@ -68,7 +70,7 @@ Future<(List<Video>, bool, List<String>)> getVideoList(
episode.url,
episode.archivePath,
);
return (videos, false, [infohash ?? ""]);
return (videos, false, [infohash ?? ""], mpvDirectory);
}
try {
@ -91,7 +93,7 @@ Future<(List<Video>, bool, List<String>)> getVideoList(
}
}
}
return (torrentList, false, infoHashes);
return (torrentList, false, infoHashes, mpvDirectory);
}
List<Video> list = await getExtensionService(
@ -105,5 +107,5 @@ Future<(List<Video>, bool, List<String>)> getVideoList(
}
}
return (videos, false, infoHashes);
return (videos, false, infoHashes, mpvDirectory);
}

View file

@ -35,7 +35,7 @@ const getVideoListProvider = GetVideoListFamily();
/// See also [getVideoList].
class GetVideoListFamily
extends Family<AsyncValue<(List<Video>, bool, List<String>)>> {
extends Family<AsyncValue<(List<Video>, bool, List<String>, Directory?)>> {
/// See also [getVideoList].
const GetVideoListFamily();
@ -73,8 +73,8 @@ class GetVideoListFamily
}
/// See also [getVideoList].
class GetVideoListProvider
extends AutoDisposeFutureProvider<(List<Video>, bool, List<String>)> {
class GetVideoListProvider extends AutoDisposeFutureProvider<
(List<Video>, bool, List<String>, Directory?)> {
/// See also [getVideoList].
GetVideoListProvider({
required Chapter episode,
@ -109,7 +109,7 @@ class GetVideoListProvider
@override
Override overrideWith(
FutureOr<(List<Video>, bool, List<String>)> Function(
FutureOr<(List<Video>, bool, List<String>, Directory?)> Function(
GetVideoListRef provider)
create,
) {
@ -128,8 +128,8 @@ class GetVideoListProvider
}
@override
AutoDisposeFutureProviderElement<(List<Video>, bool, List<String>)>
createElement() {
AutoDisposeFutureProviderElement<
(List<Video>, bool, List<String>, Directory?)> createElement() {
return _GetVideoListProviderElement(this);
}
@ -149,15 +149,14 @@ class GetVideoListProvider
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin GetVideoListRef
on AutoDisposeFutureProviderRef<(List<Video>, bool, List<String>)> {
mixin GetVideoListRef on AutoDisposeFutureProviderRef<
(List<Video>, bool, List<String>, Directory?)> {
/// The parameter `episode` of this provider.
Chapter get episode;
}
class _GetVideoListProviderElement
extends AutoDisposeFutureProviderElement<(List<Video>, bool, List<String>)>
with GetVideoListRef {
class _GetVideoListProviderElement extends AutoDisposeFutureProviderElement<
(List<Video>, bool, List<String>, Directory?)> with GetVideoListRef {
_GetVideoListProviderElement(super.provider);
@override

View file

@ -5,7 +5,7 @@ import 'dart:ui';
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/reader_view.dart';
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
import 'package:mangayomi/utils/headers.dart';

View file

@ -1,3 +1,6 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
extension StringExtensions on String {
String substringAfter(String pattern) {
final startIndex = indexOf(pattern);
@ -73,3 +76,22 @@ extension StringExtensions on String {
].any((extension) => toLowerCase().endsWith(extension));
}
}
extension NativeStringExtensions on List<String> {
Pointer<Pointer<Int8>> strListToPointer() {
final strings = this;
List<Pointer<Int8>> int8PointerList = strings
.map((str) => str.toNativeUtf8().cast<Int8>())
.toList();
final Pointer<Pointer<Int8>> pointerPointer = malloc.allocate(
int8PointerList.length,
);
strings.asMap().forEach((index, utf) {
pointerPointer[index] = int8PointerList[index];
});
return pointerPointer;
}
}

View file

@ -1160,12 +1160,13 @@ packages:
source: hosted
version: "0.5.3"
media_kit:
dependency: "direct main"
dependency: transitive
description:
name: media_kit
sha256: "48c10c3785df5d88f0eef970743f8c99b2e5da2b34b9d8f9876e598f62d9e776"
url: "https://pub.dev"
source: hosted
path: media_kit
ref: HEAD
resolved-ref: "746465b7914fa524781813dc6e50ea87dcd686e5"
url: "https://github.com/Schnitzel5/media-kit.git"
source: git
version: "1.2.0"
media_kit_libs_android_video:
dependency: transitive
@ -1219,9 +1220,9 @@ packages:
dependency: "direct main"
description:
path: media_kit_video
ref: aeb29faa8ea93a386ad1185b69fd6225fa331c74
resolved-ref: aeb29faa8ea93a386ad1185b69fd6225fa331c74
url: "https://github.com/media-kit/media-kit.git"
ref: HEAD
resolved-ref: "746465b7914fa524781813dc6e50ea87dcd686e5"
url: "https://github.com/Schnitzel5/media-kit.git"
source: git
version: "1.3.0"
meta:
@ -2081,10 +2082,10 @@ packages:
dependency: transitive
description:
name: volume_controller
sha256: e82fd689bb8e1fe8e64be3fa5946ff8699058f8cf9f4c1679acdba20cda7f5bd
sha256: d75039e69c0d90e7810bfd47e3eedf29ff8543ea7a10392792e81f9bded7edf5
url: "https://pub.dev"
source: hosted
version: "3.3.3"
version: "3.4.0"
wakelock_plus:
dependency: transitive
description:

View file

@ -41,12 +41,10 @@ dependencies:
flutter_web_auth_2: ^3.1.2
numberpicker: ^2.1.2
encrypt: ^5.0.3
media_kit: ^1.2.0
media_kit_video:
git:
url: https://github.com/media-kit/media-kit.git
url: https://github.com/Schnitzel5/media-kit.git
path: media_kit_video
ref: aeb29faa8ea93a386ad1185b69fd6225fa331c74
media_kit_libs_video: ^1.0.6
crypto: ^3.0.6
cupertino_icons: ^1.0.8
@ -157,4 +155,3 @@ inno_bundle:
- german
admin: false
version: 0.6.3