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:
kodjomoustapha 2023-12-27 23:04:19 +01:00
parent 2e1c29ae8a
commit a7773f9435
20 changed files with 1451 additions and 100 deletions

View file

@ -2,5 +2,8 @@
"java.configuration.updateBuildConfiguration": "automatic",
"githubPullRequests.ignoredPullRequestBranches": [
"main"
],
"rust-analyzer.linkedProjects": [
".\\native\\hub\\Cargo.toml"
]
}

902
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

@ -0,0 +1,9 @@
syntax = "proto3";
package rust_js;
message ReadRequest {
string code_script = 1;
}
message ReadResponse {
string response = 1;
}

View file

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

View file

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

View file

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