mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-01-11 22:40:36 +00:00
Merge branch 'temp/mpv-anime4k' into feature/mpv-anime4k
This commit is contained in:
commit
38728ce0cb
49 changed files with 1690 additions and 382 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`).
|
||||
|
|
|
|||
|
|
@ -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?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`).
|
||||
|
|
|
|||
|
|
@ -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?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
54
lib/modules/manga/reader/u_chap_data_preload.dart
Normal file
54
lib/modules/manga/reader/u_chap_data_preload.dart
Normal 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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
21
pubspec.lock
21
pubspec.lock
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue