Added Many features
- Added boa_engine(for JS runtime) from Rust - Added some method for Element and Document Wrappers - Added some cool views to Player - Refactor
This commit is contained in:
parent
2e1c29ae8a
commit
a7773f9435
20 changed files with 1451 additions and 100 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
|
@ -2,5 +2,8 @@
|
|||
"java.configuration.updateBuildConfiguration": "automatic",
|
||||
"githubPullRequests.ignoredPullRequestBranches": [
|
||||
"main"
|
||||
],
|
||||
"rust-analyzer.linkedProjects": [
|
||||
".\\native\\hub\\Cargo.toml"
|
||||
]
|
||||
}
|
||||
902
Cargo.lock
generated
902
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -75,6 +75,64 @@ class $MDocument implements MDocument, $Instance {
|
|||
false)
|
||||
]),
|
||||
),
|
||||
'getElementsByClassName': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(
|
||||
BridgeTypeRef(CoreTypes.list, [$type]),
|
||||
nullable: true),
|
||||
params: [
|
||||
BridgeParameter(
|
||||
'classNames',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
false)
|
||||
]),
|
||||
),
|
||||
'getElementsByTagName': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(
|
||||
BridgeTypeRef(CoreTypes.list, [$type]),
|
||||
nullable: true),
|
||||
params: [
|
||||
BridgeParameter(
|
||||
'localNames',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
false)
|
||||
]),
|
||||
),
|
||||
'getElementById': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation($MElement.$type, nullable: true),
|
||||
params: [
|
||||
BridgeParameter(
|
||||
'id',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
false)
|
||||
]),
|
||||
),
|
||||
'xpath': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(
|
||||
BridgeTypeRef(
|
||||
CoreTypes.list, [BridgeTypeRef(CoreTypes.string)]),
|
||||
nullable: true),
|
||||
params: [
|
||||
BridgeParameter(
|
||||
'xpath',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
false)
|
||||
]),
|
||||
),
|
||||
'xpathFirst': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string),
|
||||
nullable: true),
|
||||
params: [
|
||||
BridgeParameter(
|
||||
'xpath',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
false)
|
||||
]),
|
||||
),
|
||||
},
|
||||
wrap: true);
|
||||
|
||||
|
|
@ -116,6 +174,16 @@ class $MDocument implements MDocument, $Instance {
|
|||
return __select;
|
||||
case 'selectFirst':
|
||||
return __selectFirst;
|
||||
case 'getElementsByClassName':
|
||||
return __getElementsByClassName;
|
||||
case 'getElementsByTagName':
|
||||
return __getElementsByTagName;
|
||||
case 'getElementById':
|
||||
return __getElementById;
|
||||
case 'xpath':
|
||||
return __xpath;
|
||||
case 'xpathFirst':
|
||||
return __xpathFirst;
|
||||
default:
|
||||
return _superclass.$getProperty(runtime, identifier);
|
||||
}
|
||||
|
|
@ -148,12 +216,74 @@ class $MDocument implements MDocument, $Instance {
|
|||
return $MDocument.wrap(MDocument(args[0]?.$value));
|
||||
}
|
||||
|
||||
static const $Function __getElementsByClassName =
|
||||
$Function(_getElementsByClassName);
|
||||
static $Value? _getElementsByClassName(
|
||||
final Runtime runtime, final $Value? target, final List<$Value?> args) {
|
||||
final res =
|
||||
(target!.$value as MDocument).getElementsByClassName(args[0]?.$value);
|
||||
return res == null
|
||||
? const $null()
|
||||
: $List.wrap(res.map((e) => $MElement.wrap(e)).toList());
|
||||
}
|
||||
|
||||
static const $Function __getElementsByTagName =
|
||||
$Function(_getElementsByTagName);
|
||||
static $Value? _getElementsByTagName(
|
||||
final Runtime runtime, final $Value? target, final List<$Value?> args) {
|
||||
final res =
|
||||
(target!.$value as MDocument).getElementsByTagName(args[0]?.$value);
|
||||
return res == null
|
||||
? const $null()
|
||||
: $List.wrap(res.map((e) => $MElement.wrap(e)).toList());
|
||||
}
|
||||
|
||||
static const $Function __getElementById = $Function(_getElementById);
|
||||
static $Value? _getElementById(
|
||||
final Runtime runtime, final $Value? target, final List<$Value?> args) {
|
||||
final res = (target!.$value as MDocument).getElementById(args[0]?.$value);
|
||||
return res == null ? const $null() : $MElement.wrap(res);
|
||||
}
|
||||
|
||||
static const $Function __xpath = $Function(_xpath);
|
||||
static $Value? _xpath(
|
||||
final Runtime runtime, final $Value? target, final List<$Value?> args) {
|
||||
final res = (target!.$value as MDocument).xpath(args[0]?.$value);
|
||||
return res == null
|
||||
? const $null()
|
||||
: $List.wrap(res.map((e) => $String(e)).toList());
|
||||
}
|
||||
|
||||
static const $Function __xpathFirst = $Function(_xpathFirst);
|
||||
static $Value? _xpathFirst(
|
||||
final Runtime runtime, final $Value? target, final List<$Value?> args) {
|
||||
final res = (target!.$value as MDocument).xpathFirst(args[0]?.$value);
|
||||
return res == null ? const $null() : $String(res);
|
||||
}
|
||||
|
||||
@override
|
||||
List<MElement>? select(String selector) => $value.select(selector);
|
||||
|
||||
@override
|
||||
MElement? selectFirst(String selector) => $value.selectFirst(selector);
|
||||
|
||||
@override
|
||||
List<String>? xpath(String xpath) => $value.xpath(xpath);
|
||||
|
||||
@override
|
||||
String? xpathFirst(String xpath) => $value.xpathFirst(xpath);
|
||||
|
||||
@override
|
||||
List<MElement>? getElementsByClassName(String classNames) =>
|
||||
$value.getElementsByClassName(classNames);
|
||||
|
||||
@override
|
||||
List<MElement>? getElementsByTagName(String localNames) =>
|
||||
$value.getElementsByTagName(localNames);
|
||||
|
||||
@override
|
||||
MElement? getElementById(String id) => $value.getElementById(id);
|
||||
|
||||
@override
|
||||
MElement? get body => $value.body;
|
||||
|
||||
|
|
|
|||
|
|
@ -115,6 +115,54 @@ class $MElement implements MElement, $Instance {
|
|||
false)
|
||||
]),
|
||||
),
|
||||
'getElementsByClassName': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(
|
||||
BridgeTypeRef(CoreTypes.list, [$type]),
|
||||
nullable: true),
|
||||
params: [
|
||||
BridgeParameter(
|
||||
'classNames',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
false)
|
||||
]),
|
||||
),
|
||||
'getElementsByTagName': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(
|
||||
BridgeTypeRef(CoreTypes.list, [$type]),
|
||||
nullable: true),
|
||||
params: [
|
||||
BridgeParameter(
|
||||
'localNames',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
false)
|
||||
]),
|
||||
),
|
||||
'xpath': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(
|
||||
BridgeTypeRef(
|
||||
CoreTypes.list, [BridgeTypeRef(CoreTypes.string)]),
|
||||
nullable: true),
|
||||
params: [
|
||||
BridgeParameter(
|
||||
'xpath',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
false)
|
||||
]),
|
||||
),
|
||||
'xpathFirst': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string),
|
||||
nullable: true),
|
||||
params: [
|
||||
BridgeParameter(
|
||||
'xpath',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
false)
|
||||
]),
|
||||
),
|
||||
},
|
||||
wrap: true);
|
||||
|
||||
|
|
@ -137,28 +185,44 @@ class $MElement implements MElement, $Instance {
|
|||
return res == null ? const $null() : $String(res);
|
||||
case 'text':
|
||||
final res = $value.text;
|
||||
return res == null ? const $null() : $String(res.trim().trimLeft().trimRight());
|
||||
return res == null
|
||||
? const $null()
|
||||
: $String(res.trim().trimLeft().trimRight());
|
||||
case 'className':
|
||||
final res = $value.className;
|
||||
return res == null ? const $null() : $String(res.trim().trimLeft().trimRight());
|
||||
return res == null
|
||||
? const $null()
|
||||
: $String(res.trim().trimLeft().trimRight());
|
||||
case 'localName':
|
||||
final res = $value.localName;
|
||||
return res == null ? const $null() : $String(res.trim().trimLeft().trimRight());
|
||||
return res == null
|
||||
? const $null()
|
||||
: $String(res.trim().trimLeft().trimRight());
|
||||
case 'namespaceUri':
|
||||
final res = $value.namespaceUri;
|
||||
return res == null ? const $null() : $String(res.trim().trimLeft().trimRight());
|
||||
return res == null
|
||||
? const $null()
|
||||
: $String(res.trim().trimLeft().trimRight());
|
||||
case 'getSrc':
|
||||
final res = $value.getSrc;
|
||||
return res == null ? const $null() : $String(res.trim().trimLeft().trimRight());
|
||||
return res == null
|
||||
? const $null()
|
||||
: $String(res.trim().trimLeft().trimRight());
|
||||
case 'getImg':
|
||||
final res = $value.getImg;
|
||||
return res == null ? const $null() : $String(res.trim().trimLeft().trimRight());
|
||||
return res == null
|
||||
? const $null()
|
||||
: $String(res.trim().trimLeft().trimRight());
|
||||
case 'getHref':
|
||||
final res = $value.getHref;
|
||||
return res == null ? const $null() : $String(res.trim().trimLeft().trimRight());
|
||||
return res == null
|
||||
? const $null()
|
||||
: $String(res.trim().trimLeft().trimRight());
|
||||
case 'getDataSrc':
|
||||
final res = $value.getDataSrc;
|
||||
return res == null ? const $null() : $String(res.trim().trimLeft().trimRight());
|
||||
return res == null
|
||||
? const $null()
|
||||
: $String(res.trim().trimLeft().trimRight());
|
||||
case 'parent':
|
||||
final res = $value.parent;
|
||||
return res == null ? const $null() : $MElement.wrap(res);
|
||||
|
|
@ -174,6 +238,14 @@ class $MElement implements MElement, $Instance {
|
|||
return __select;
|
||||
case 'selectFirst':
|
||||
return __selectFirst;
|
||||
case 'getElementsByClassName':
|
||||
return __getElementsByClassName;
|
||||
case 'getElementsByTagName':
|
||||
return __getElementsByTagName;
|
||||
case 'xpath':
|
||||
return __xpath;
|
||||
case 'xpathFirst':
|
||||
return __xpathFirst;
|
||||
default:
|
||||
return _superclass.$getProperty(runtime, identifier);
|
||||
}
|
||||
|
|
@ -213,6 +285,50 @@ class $MElement implements MElement, $Instance {
|
|||
return res == null ? const $null() : $MElement.wrap(res);
|
||||
}
|
||||
|
||||
static const $Function __getElementsByClassName =
|
||||
$Function(_getElementsByClassName);
|
||||
static $Value? _getElementsByClassName(
|
||||
final Runtime runtime, final $Value? target, final List<$Value?> args) {
|
||||
final res =
|
||||
(target!.$value as MElement).getElementsByClassName(args[0]?.$value);
|
||||
return res == null
|
||||
? const $null()
|
||||
: $List.wrap(res.map((e) => $MElement.wrap(e)).toList());
|
||||
}
|
||||
|
||||
static const $Function __getElementsByTagName =
|
||||
$Function(_getElementsByTagName);
|
||||
static $Value? _getElementsByTagName(
|
||||
final Runtime runtime, final $Value? target, final List<$Value?> args) {
|
||||
final res =
|
||||
(target!.$value as MElement).getElementsByTagName(args[0]?.$value);
|
||||
return res == null
|
||||
? const $null()
|
||||
: $List.wrap(res.map((e) => $MElement.wrap(e)).toList());
|
||||
}
|
||||
|
||||
static const $Function __xpath = $Function(_xpath);
|
||||
static $Value? _xpath(
|
||||
final Runtime runtime, final $Value? target, final List<$Value?> args) {
|
||||
final res = (target!.$value as MElement).xpath(args[0]?.$value);
|
||||
return res == null
|
||||
? const $null()
|
||||
: $List.wrap(res.map((e) => $String(e)).toList());
|
||||
}
|
||||
|
||||
static const $Function __xpathFirst = $Function(_xpathFirst);
|
||||
static $Value? _xpathFirst(
|
||||
final Runtime runtime, final $Value? target, final List<$Value?> args) {
|
||||
final res = (target!.$value as MElement).xpathFirst(args[0]?.$value);
|
||||
return res == null ? const $null() : $String(res);
|
||||
}
|
||||
|
||||
@override
|
||||
List<String>? xpath(String xpath) => $value.xpath(xpath);
|
||||
|
||||
@override
|
||||
String? xpathFirst(String xpath) => $value.xpathFirst(xpath);
|
||||
|
||||
@override
|
||||
String? attr(String attr) => $value.attr(attr);
|
||||
|
||||
|
|
@ -225,6 +341,14 @@ class $MElement implements MElement, $Instance {
|
|||
@override
|
||||
List<MElement>? get children => $value.children;
|
||||
|
||||
@override
|
||||
List<MElement>? getElementsByClassName(String classNames) =>
|
||||
$value.getElementsByClassName(classNames);
|
||||
|
||||
@override
|
||||
List<MElement>? getElementsByTagName(String localNames) =>
|
||||
$value.getElementsByTagName(localNames);
|
||||
|
||||
@override
|
||||
String? get className => $value.className;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import 'package:mangayomi/eval/model/m_manga.dart';
|
|||
import 'package:mangayomi/eval/model/m_provider.dart';
|
||||
import 'package:mangayomi/models/video.dart';
|
||||
import 'package:mangayomi/modules/browse/extension/providers/extension_preferences_providers.dart';
|
||||
import 'package:mangayomi/services/eval_js.dart';
|
||||
|
||||
class $MProvider extends MProvider with $Bridge<MProvider> {
|
||||
static $MProvider $construct(
|
||||
|
|
@ -667,7 +668,7 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
|
|||
false),
|
||||
]),
|
||||
),
|
||||
'evalJs': BridgeMethodDef(
|
||||
'unpackJs': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
params: [
|
||||
|
|
@ -677,6 +678,17 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
|
|||
false),
|
||||
]),
|
||||
),
|
||||
'evalJs': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(BridgeTypeRef(
|
||||
CoreTypes.future, [BridgeTypeRef(CoreTypes.string)])),
|
||||
params: [
|
||||
BridgeParameter(
|
||||
'code',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
false),
|
||||
]),
|
||||
),
|
||||
'regExp': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
|
|
@ -737,6 +749,10 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
|
|||
@override
|
||||
$Value? $bridgeGet(String identifier) {
|
||||
return switch (identifier) {
|
||||
'evalJs' => $Function((_, __, List<$Value?> args) {
|
||||
return $Future
|
||||
.wrap(evalJs(args[0]!.$reified).then((value) => $String(value)));
|
||||
}),
|
||||
'http' => $Function((_, __, List<$Value?> args) {
|
||||
return $Future.wrap(MBridge.http(args[0]!.$reified, args[1]!.$reified)
|
||||
.then((value) => $String(value)));
|
||||
|
|
@ -868,7 +884,7 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
|
|||
MBridge.getHtmlViaWebview(args[0]!.$value, args[1]!.$value)
|
||||
.then((value) => $String(value)));
|
||||
}),
|
||||
"evalJs" => MBridge.evalJs,
|
||||
"unpackJs" => MBridge.unpackJs,
|
||||
"regExp" => $Function((_, __, List<$Value?> args) {
|
||||
return $String(MBridge.regExp(args[0]!.$value, args[1]!.$value,
|
||||
args[2]!.$value, args[3]!.$value, args[4]!.$value));
|
||||
|
|
|
|||
|
|
@ -26,6 +26,36 @@ class MDocument {
|
|||
return _document?.select(selector)?.map((e) => MElement(e)).toList();
|
||||
}
|
||||
|
||||
String? xpathFirst(String xpath) {
|
||||
return _document?.outerHtml == null
|
||||
? null
|
||||
: _document?.xpathFirst(xpath, _document.outerHtml);
|
||||
}
|
||||
|
||||
List<String>? xpath(String xpath) {
|
||||
return _document?.outerHtml == null
|
||||
? null
|
||||
: _document?.xpath(xpath, _document.outerHtml);
|
||||
}
|
||||
|
||||
List<MElement>? getElementsByClassName(String classNames) {
|
||||
return _document
|
||||
?.getElementsByClassName(classNames)
|
||||
.map((e) => MElement(e))
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<MElement>? getElementsByTagName(String localNames) {
|
||||
return _document
|
||||
?.getElementsByTagName(localNames)
|
||||
.map((e) => MElement(e))
|
||||
.toList();
|
||||
}
|
||||
|
||||
MElement? getElementById(String id) {
|
||||
return MElement(_document?.getElementById(id));
|
||||
}
|
||||
|
||||
MElement? selectFirst(String selector) {
|
||||
return MElement(_document?.selectFirst(selector));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,11 +32,32 @@ class MElement {
|
|||
MElement? get parent => MElement(_element?.parent);
|
||||
|
||||
MElement? get nextElementSibling => MElement(_element?.nextElementSibling);
|
||||
|
||||
|
||||
MElement? get previousElementSibling =>
|
||||
MElement(_element?.previousElementSibling);
|
||||
|
||||
String? xpathFirst(String xpath) {
|
||||
return _element?.outerHtml == null ? null : _element?.xpathFirst(xpath);
|
||||
}
|
||||
|
||||
List<String>? xpath(String xpath) {
|
||||
return _element?.outerHtml == null ? null : _element?.xpath(xpath);
|
||||
}
|
||||
|
||||
List<MElement>? getElementsByClassName(String classNames) {
|
||||
return _element
|
||||
?.getElementsByClassName(classNames)
|
||||
.map((e) => MElement(e))
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<MElement>? getElementsByTagName(String localNames) {
|
||||
return _element
|
||||
?.getElementsByTagName(localNames)
|
||||
.map((e) => MElement(e))
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<MElement>? select(String selector) {
|
||||
return _element?.select(selector)?.map((e) => MElement(e)).toList();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,9 +200,9 @@ class MBridge {
|
|||
}
|
||||
|
||||
///Unpack a JS code
|
||||
static const $Function evalJs = $Function(_evalJs);
|
||||
static const $Function unpackJs = $Function(_unpackJs);
|
||||
|
||||
static $Value? _evalJs(_, __, List<$Value?> args) {
|
||||
static $Value? _unpackJs(_, __, List<$Value?> args) {
|
||||
String code = args[0]!.$reified;
|
||||
try {
|
||||
final jsPacker = JSPacker(code);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import 'package:mangayomi/utils/media_query.dart';
|
|||
import 'package:media_kit/media_kit.dart';
|
||||
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:screen_brightness/screen_brightness.dart';
|
||||
|
||||
class AnimePlayerView extends riv.ConsumerStatefulWidget {
|
||||
final Chapter episode;
|
||||
|
|
@ -30,22 +31,12 @@ class AnimePlayerView extends riv.ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _AnimePlayerViewState extends riv.ConsumerState<AnimePlayerView> {
|
||||
@override
|
||||
void dispose() {
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
DeviceOrientation.landscapeLeft,
|
||||
DeviceOrientation.landscapeRight
|
||||
]);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final serversData = ref.watch(getVideoListProvider(
|
||||
episode: widget.episode,
|
||||
));
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
|
||||
return serversData.when(
|
||||
data: (data) {
|
||||
if (data.$1.isEmpty &&
|
||||
|
|
@ -178,15 +169,14 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
final ValueNotifier<double> _playbackSpeed = ValueNotifier(1.0);
|
||||
bool _seekToCurrentPosition = true;
|
||||
bool _initSubtitle = true;
|
||||
final ValueNotifier<bool> _enterFullScreen = ValueNotifier(false);
|
||||
late final ValueNotifier<Duration> _currentPosition =
|
||||
ValueNotifier(_streamController.geTCurrentPosition());
|
||||
final ValueNotifier<Duration?> _currentTotalDuration = ValueNotifier(null);
|
||||
final ValueNotifier<bool> _showFitLabel = ValueNotifier(false);
|
||||
final ValueNotifier<bool> _showSeekTo = ValueNotifier(false);
|
||||
final ValueNotifier<bool> _isCompleted = ValueNotifier(false);
|
||||
final ValueNotifier<Duration?> _tempPosition = ValueNotifier(null);
|
||||
final ValueNotifier<BoxFit> _fit = ValueNotifier(BoxFit.contain);
|
||||
final ValueNotifier<int> _seekTo = ValueNotifier(0);
|
||||
|
||||
final bool _isDesktop =
|
||||
Platform.isWindows || Platform.isMacOS || Platform.isLinux;
|
||||
|
|
@ -221,7 +211,7 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
_currentTotalDuration.value = duration;
|
||||
},
|
||||
);
|
||||
|
||||
double _brightnessValue = 0.0;
|
||||
@override
|
||||
void initState() {
|
||||
_setCurrentPosition(true);
|
||||
|
|
@ -230,6 +220,18 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
_player.open(Media(_video.value!.videoTrack!.id,
|
||||
httpHeaders: _video.value!.headers));
|
||||
_setPlaybackSpeed(ref.read(defaultPlayBackSpeedStateProvider));
|
||||
Future.microtask(() async {
|
||||
try {
|
||||
_brightnessValue = await ScreenBrightness().current;
|
||||
ScreenBrightness().onCurrentBrightnessChanged.listen((value) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_brightnessValue = value;
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (_) {}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
|
@ -239,6 +241,7 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
_player.dispose();
|
||||
_currentPositionSub.cancel();
|
||||
_currentTotalDurationSub.cancel();
|
||||
_setFullscreen(false);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -249,6 +252,20 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
_streamController.setAnimeHistoryUpdate();
|
||||
}
|
||||
|
||||
void _setFullscreen(bool state) {
|
||||
if (state) {
|
||||
SystemChrome.setPreferredOrientations(
|
||||
[DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
|
||||
} else {
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
DeviceOrientation.landscapeLeft,
|
||||
DeviceOrientation.landscapeRight
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _videoQualityWidget(BuildContext context) {
|
||||
List<VideoPrefs> videoQuality = _player.state.tracks.video
|
||||
.where((element) =>
|
||||
|
|
@ -675,23 +692,27 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
Widget _seekToWidget() {
|
||||
final defaultSkipIntroLength =
|
||||
ref.watch(defaultSkipIntroLengthStateProvider);
|
||||
return SizedBox(
|
||||
height: 35,
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
_seekTo.value = defaultSkipIntroLength;
|
||||
_showSeekTo.value = true;
|
||||
await _player.seek(Duration(
|
||||
seconds:
|
||||
_currentPosition.value.inSeconds + defaultSkipIntroLength));
|
||||
_seekTo.value = 0;
|
||||
_showSeekTo.value = false;
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text("+$defaultSkipIntroLength",
|
||||
style: const TextStyle(fontWeight: FontWeight.w100)),
|
||||
)),
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: SizedBox(
|
||||
height: 35,
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
_tempPosition.value = Duration(
|
||||
seconds: defaultSkipIntroLength +
|
||||
_currentPosition.value.inSeconds -
|
||||
_currentPosition.value.inSeconds);
|
||||
await _player.seek(Duration(
|
||||
seconds: _currentPosition.value.inSeconds +
|
||||
defaultSkipIntroLength));
|
||||
_tempPosition.value = null;
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text("+$defaultSkipIntroLength",
|
||||
style: const TextStyle(fontWeight: FontWeight.w100)),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -738,7 +759,21 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
_changeFitLabel(ref);
|
||||
},
|
||||
),
|
||||
const MaterialFullscreenButton()
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _enterFullScreen,
|
||||
builder: (context, snapshot, _) {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
_setFullscreen(!snapshot);
|
||||
_enterFullScreen.value = !snapshot;
|
||||
},
|
||||
icon: Icon(snapshot
|
||||
? Icons.fullscreen_exit
|
||||
: Icons.fullscreen),
|
||||
iconSize: 25,
|
||||
color: Colors.white,
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
@ -783,14 +818,17 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
child: CustomSeekBar(
|
||||
player: _controller.player,
|
||||
onSeekStart: (start) {
|
||||
_tempPosition.value = start;
|
||||
},
|
||||
onSeekEnd: (end) {
|
||||
_tempPosition.value = null;
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: CustomSeekBar(
|
||||
player: _controller.player,
|
||||
onSeekStart: (start) {
|
||||
_tempPosition.value = start;
|
||||
},
|
||||
onSeekEnd: (end) {
|
||||
_tempPosition.value = null;
|
||||
},
|
||||
),
|
||||
)),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
|
@ -876,8 +914,7 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
Flexible(
|
||||
child: Row(
|
||||
children: [
|
||||
if (isFullScreen &&
|
||||
(Platform.isIOS || Platform.isMacOS || Platform.isAndroid)) ...[
|
||||
if (isFullScreen && (Platform.isIOS || Platform.isAndroid)) ...[
|
||||
MaterialFullscreenButton(
|
||||
icon: Icon(Platform.isIOS || Platform.isMacOS
|
||||
? Icons.arrow_back_ios
|
||||
|
|
@ -885,8 +922,10 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
)
|
||||
] else ...[
|
||||
if (isFullScreen)
|
||||
const MaterialDesktopFullscreenButton(
|
||||
icon: Icon(Icons.arrow_back))
|
||||
MaterialDesktopFullscreenButton(
|
||||
icon: Icon(Platform.isMacOS
|
||||
? Icons.arrow_back_ios
|
||||
: Icons.arrow_back))
|
||||
],
|
||||
if (!isFullScreen)
|
||||
BackButton(
|
||||
|
|
@ -960,24 +999,6 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
height: mediaHeight(context, 1),
|
||||
resumeUponEnteringForegroundMode: true,
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _showSeekTo,
|
||||
builder: (context, showSeekTo, child) => showSeekTo
|
||||
? ValueListenableBuilder(
|
||||
valueListenable: _seekTo,
|
||||
builder: (context, seekTo, child) => Positioned.fill(
|
||||
child: UnconstrainedBox(
|
||||
child: Text(
|
||||
"[+${Duration(seconds: seekTo).label()}]",
|
||||
style: const TextStyle(
|
||||
fontSize: 40.0,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container()),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _showFitLabel,
|
||||
builder: (context, showFitLabel, child) => showFitLabel
|
||||
|
|
@ -1009,8 +1030,8 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
seekOnDoubleTap: true,
|
||||
seekGesture: true,
|
||||
horizontalGestureSensitivity: 5000,
|
||||
verticalGestureSensitivity: 1000,
|
||||
controlsHoverDuration: const Duration(seconds: 10000),
|
||||
verticalGestureSensitivity: 500,
|
||||
controlsHoverDuration: const Duration(seconds: 15),
|
||||
volumeGesture: true,
|
||||
brightnessGesture: true,
|
||||
seekBarThumbSize: 15,
|
||||
|
|
@ -1018,8 +1039,8 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
displaySeekBar: false,
|
||||
volumeIndicatorBuilder: (_, value) =>
|
||||
MediaIndicatorBuilder(value: value, isVolumeIndicator: true),
|
||||
brightnessIndicatorBuilder: (_, value) =>
|
||||
MediaIndicatorBuilder(value: value, isVolumeIndicator: false),
|
||||
brightnessIndicatorBuilder: (_, value) => MediaIndicatorBuilder(
|
||||
value: _brightnessValue, isVolumeIndicator: false),
|
||||
seekIndicatorBuilder: (context, duration) {
|
||||
return _seekIndicatorTextWidget(duration, _currentPosition.value);
|
||||
},
|
||||
|
|
@ -1116,7 +1137,7 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
: const SizedBox.shrink();
|
||||
})
|
||||
],
|
||||
buttonBarHeight: 110,
|
||||
buttonBarHeight: 120,
|
||||
displaySeekBar: false,
|
||||
seekBarThumbSize: 15,
|
||||
bottomButtonBar: _desktopBottomButtonBar(context, isFullScreen));
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class MediaIndicatorBuilder extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
isVolumeIndicator ? MainAxisAlignment.start : MainAxisAlignment.end,
|
||||
|
|
@ -26,7 +26,10 @@ class MediaIndicatorBuilder extends StatelessWidget {
|
|||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
Text((value * 100).ceil().toString()),
|
||||
Text(
|
||||
(value * 100).ceil().toString(),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: RotatedBox(
|
||||
|
|
@ -34,15 +37,14 @@ class MediaIndicatorBuilder extends StatelessWidget {
|
|||
child: Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white54,
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
),
|
||||
child: SizedBox.fromSize(
|
||||
size: const Size(170, 10),
|
||||
child: LinearProgressIndicator(
|
||||
value: value,
|
||||
backgroundColor: Colors.transparent),
|
||||
),
|
||||
size: const Size(130, 20),
|
||||
child: LinearProgressIndicator(
|
||||
value: value,
|
||||
backgroundColor: Colors.transparent)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import 'package:mangayomi/modules/more/about/providers/check_for_update.dart';
|
|||
import 'package:mangayomi/modules/more/backup_and_restore/providers/auto_backup.dart';
|
||||
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
|
||||
import 'package:mangayomi/modules/widgets/error_text.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/router/router.dart';
|
||||
import 'package:mangayomi/utils/colors.dart';
|
||||
|
|
@ -273,7 +272,17 @@ class MainScreen extends ConsumerWidget {
|
|||
}, error: (Object error, StackTrace stackTrace) {
|
||||
return ErrorText(error);
|
||||
}, loading: () {
|
||||
return const ProgressCenter();
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: Center(
|
||||
child: Image.asset(
|
||||
"assets/app_icons/icon.png",
|
||||
color: Colors.black,
|
||||
fit: BoxFit.fill,
|
||||
height: 100,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class DoodExtractor {
|
|||
Uri.parse('https://$doodHost/pass_md5/$md5'),
|
||||
headers: {'referer': newUrl},
|
||||
);
|
||||
if (videoUrlStart.statusCode != 200) return [];
|
||||
|
||||
final videoUrl =
|
||||
'${videoUrlStart.body}$randomString?token=$token&expiry=$expiry';
|
||||
|
|
|
|||
|
|
@ -19,8 +19,16 @@ class StreamWishExtractor {
|
|||
if (jsEval.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
String? masterUrl = (JSPacker(jsEval.first!).unpack() ?? "")
|
||||
|
||||
String? masterUrl = jsEval.first
|
||||
.let(
|
||||
(script) {
|
||||
if (script!.contains("function(p,a,c")) {
|
||||
return JSPacker(script).unpack() ?? "";
|
||||
}
|
||||
return script;
|
||||
},
|
||||
)
|
||||
.substringAfter('source')
|
||||
.substringAfter('file:"')
|
||||
.substringBefore('"');
|
||||
|
|
|
|||
18
lib/services/eval_js.dart
Normal file
18
lib/services/eval_js.dart
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import 'package:mangayomi/messages/rust_js.pb.dart' as rust_js;
|
||||
import 'package:rinf/rinf.dart';
|
||||
|
||||
Future<String> evalJs(String script) async {
|
||||
final requestMessage = rust_js.ReadRequest(codeScript: script);
|
||||
final rustRequest = RustRequest(
|
||||
resource: rust_js.ID,
|
||||
operation: RustOperation.Read,
|
||||
message: requestMessage.writeToBuffer());
|
||||
final rustResponse = await requestToRust(rustRequest);
|
||||
if (rustResponse.successful) {
|
||||
final responseMessage = rust_js.ReadResponse.fromBuffer(
|
||||
rustResponse.message!,
|
||||
);
|
||||
return responseMessage.response;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:html/dom.dart';
|
||||
import 'package:mangayomi/utils/reg_exp_matcher.dart';
|
||||
import 'package:xpath_selector_html_parser/xpath_selector_html_parser.dart';
|
||||
|
||||
extension StringExtensions on String {
|
||||
String substringAfter(String pattern) {
|
||||
|
|
@ -65,7 +66,7 @@ extension LetExtension<T> on T {
|
|||
}
|
||||
}
|
||||
|
||||
extension SelectDocumentExtension on Document? {
|
||||
extension DocumentExtension on Document? {
|
||||
List<Element>? select(String selector) {
|
||||
try {
|
||||
return this?.querySelectorAll(selector);
|
||||
|
|
@ -81,9 +82,24 @@ extension SelectDocumentExtension on Document? {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String? xpathFirst(String xpath, String outerHtml) {
|
||||
var htmlXPath = HtmlXPath.html(outerHtml);
|
||||
var query = htmlXPath.query(xpath);
|
||||
return query.attr;
|
||||
}
|
||||
|
||||
List<String> xpath(String xpath, String outerHtml) {
|
||||
var htmlXPath = HtmlXPath.html(outerHtml);
|
||||
var query = htmlXPath.query(xpath);
|
||||
if (query.nodes.length > 1) {
|
||||
return query.attrs.map((e) => e!.trim().trimLeft().trimRight()).toList();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
extension SelectElementtExtension on Element {
|
||||
extension ElementtExtension on Element {
|
||||
List<Element>? select(String selector) {
|
||||
try {
|
||||
return querySelectorAll(selector);
|
||||
|
|
@ -92,6 +108,21 @@ extension SelectElementtExtension on Element {
|
|||
}
|
||||
}
|
||||
|
||||
String? xpathFirst(String xpath) {
|
||||
var htmlXPath = HtmlXPath.html(outerHtml);
|
||||
var query = htmlXPath.query(xpath);
|
||||
return query.attr;
|
||||
}
|
||||
|
||||
List<String> xpath(String xpath) {
|
||||
var htmlXPath = HtmlXPath.html(outerHtml);
|
||||
var query = htmlXPath.query(xpath);
|
||||
if (query.nodes.length > 1) {
|
||||
return query.attrs.map((e) => e!.trim().trimLeft().trimRight()).toList();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
Element? selectFirst(String selector) {
|
||||
try {
|
||||
return querySelector(selector);
|
||||
|
|
|
|||
9
messages/rust_js.proto
Normal file
9
messages/rust_js.proto
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
syntax = "proto3";
|
||||
package rust_js;
|
||||
|
||||
message ReadRequest {
|
||||
string code_script = 1;
|
||||
}
|
||||
message ReadResponse {
|
||||
string response = 1;
|
||||
}
|
||||
|
|
@ -17,3 +17,4 @@ tokio_with_wasm = "0.3.2"
|
|||
wasm-bindgen = "0.2.87"
|
||||
prost = "0.12.0"
|
||||
image = "0.24.7"
|
||||
boa_engine = "0.17.3"
|
||||
|
|
|
|||
32
native/hub/src/js.rs
Normal file
32
native/hub/src/js.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use crate::bridge::{RustOperation, RustRequest, RustResponse};
|
||||
use boa_engine::{Context, Source};
|
||||
use prost::Message;
|
||||
|
||||
pub async fn eval_js_sync(rust_request: RustRequest) -> RustResponse {
|
||||
use crate::messages::rust_js::{ReadRequest, ReadResponse};
|
||||
|
||||
match rust_request.operation {
|
||||
RustOperation::Create => RustResponse::default(),
|
||||
RustOperation::Read => {
|
||||
let mut context = Context::default();
|
||||
|
||||
let code_script = rust_request.message.unwrap();
|
||||
let request_message = ReadRequest::decode(code_script.as_slice()).unwrap();
|
||||
let response_message = ReadResponse {
|
||||
response: match context
|
||||
.eval(Source::from_bytes(request_message.code_script.as_bytes()))
|
||||
{
|
||||
Ok(res) => res.to_string(&mut context).unwrap().to_std_string_escaped(),
|
||||
Err(_e) => "error".to_string(),
|
||||
},
|
||||
};
|
||||
RustResponse {
|
||||
successful: true,
|
||||
message: Some(response_message.encode_to_vec()),
|
||||
blob: None,
|
||||
}
|
||||
}
|
||||
RustOperation::Delete => RustResponse::default(),
|
||||
RustOperation::Update => RustResponse::default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ mod bridge;
|
|||
mod imagecrop;
|
||||
mod messages;
|
||||
mod with_request;
|
||||
mod js;
|
||||
|
||||
/// This `hub` crate is the entry point for the Rust logic.
|
||||
/// Always use non-blocking async functions such as `tokio::fs::File::open`.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use crate::bridge::{RustRequestUnique, RustResponse, RustResponseUnique};
|
||||
use crate::imagecrop;
|
||||
use crate::js;
|
||||
use crate::messages;
|
||||
|
||||
pub async fn handle_request(request_unique: RustRequestUnique) -> RustResponseUnique {
|
||||
|
|
@ -17,6 +18,9 @@ pub async fn handle_request(request_unique: RustRequestUnique) -> RustResponseUn
|
|||
messages::crop_borders::ID => {
|
||||
imagecrop::start_croping(rust_request).await // ADD THIS BLOCK
|
||||
}
|
||||
messages::rust_js::ID => {
|
||||
js::eval_js_sync(rust_request).await // ADD THIS BLOCK
|
||||
}
|
||||
_ => RustResponse::default(),
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue