Merge pull request #150 from kodjodevf/feat/javascript_extension

Feat/javascript extension support
This commit is contained in:
Moustapha Kodjo Amadou 2024-03-23 14:24:04 +01:00 committed by GitHub
commit c7cfd040d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
197 changed files with 13215 additions and 2386 deletions

1315
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -44,7 +44,7 @@ Get the app from our [releases page](https://github.com/kodjodevf/mangayomi/rele
## Using Rust Inside Flutter
This project use Rust for the [auto-image-cropper](https://github.com/ritiek/auto-image-cropper) crate and [boa](https://github.com/boa-dev/boa) crate utilizing the capabilities of the [Rinf](https://pub.dev/packages/rinf) framework.
This project use Rust for the [auto-image-cropper](https://github.com/ritiek/auto-image-cropper) crate utilizing the capabilities of the [Rinf](https://pub.dev/packages/rinf) framework.
To run and build this app, you need to have
[Flutter SDK](https://docs.flutter.dev/get-started/install)

View file

@ -7,7 +7,8 @@
<application
android:label="Mangayomi"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon">
android:icon="@mipmap/launcher_icon"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true"

View file

@ -2,6 +2,11 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>

View file

@ -1,11 +1,10 @@
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:html/dom.dart';
import 'package:mangayomi/eval/bridge/element.dart';
import 'package:mangayomi/eval/model/document.dart';
import 'package:mangayomi/eval/model/element.dart';
import 'package:mangayomi/eval/dart/bridge/element.dart';
import 'package:mangayomi/eval/dart/model/document.dart';
import 'package:mangayomi/eval/dart/model/element.dart';
class $MDocument implements MDocument, $Instance {
$MDocument.wrap(this.$value) : _superclass = $Object($value);
@ -131,6 +130,17 @@ class $MDocument implements MDocument, $Instance {
false)
]),
),
'attr': BridgeMethodDef(
BridgeFunctionDef(
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
params: [
BridgeParameter(
'attr',
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string),
nullable: true),
false)
]),
),
},
wrap: true);
@ -182,6 +192,8 @@ class $MDocument implements MDocument, $Instance {
return __xpath;
case 'xpathFirst':
return __xpathFirst;
case 'attr':
return __attr;
default:
return _superclass.$getProperty(runtime, identifier);
}
@ -257,6 +269,13 @@ class $MDocument implements MDocument, $Instance {
return res == null ? const $null() : $String(res);
}
static const $Function __attr = $Function(_attr);
static $Value? _attr(
final Runtime runtime, final $Value? target, final List<$Value?> args) {
final res = (target!.$value as MDocument).attr(args[0]?.$value ?? "");
return res == null ? const $null() : $String(res);
}
@override
List<MElement>? select(String selector) => $value.select(selector);
@ -277,6 +296,9 @@ class $MDocument implements MDocument, $Instance {
List<MElement>? getElementsByTagName(String localNames) =>
$value.getElementsByTagName(localNames);
@override
String? attr(String attr) => $value.attr(attr);
@override
MElement? getElementById(String id) => $value.getElementById(id);

View file

@ -1,8 +1,7 @@
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:html/dom.dart';
import 'package:mangayomi/eval/model/element.dart';
import 'package:mangayomi/eval/dart/model/element.dart';
class $MElement implements MElement, $Instance {
$MElement.wrap(this.$value) : _superclass = $Object($value);

View file

@ -1,7 +1,6 @@
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:mangayomi/eval/model/filter.dart';
import 'package:mangayomi/eval/dart/model/filter.dart';
class $FilterList implements FilterList, $Instance {
$FilterList.wrap(this.$value) : _superclass = $Object($value);
@ -71,6 +70,11 @@ class $FilterList implements FilterList, $Instance {
@override
set filters(List<dynamic> filters) {}
@override
Map<String, dynamic> toJson() {
throw UnimplementedError();
}
}
class $SelectFilter implements SelectFilter, $Instance {
@ -113,26 +117,26 @@ class $SelectFilter implements SelectFilter, $Instance {
static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
return $SelectFilter.wrap(SelectFilter(
args[0]!.$value,
args[1]!.$value,
args[2]!.$value,
(args[3]!.$value as List).map((e) {
if (e is $Value) {
final value = e.$reified;
if (value is Map) {
Map<String, dynamic> map = {};
map = value.map((key, value) => MapEntry(key.toString(), value));
if (map['type'] == 'SelectOption') {
final filter = map['filter'] as Map;
return SelectFilterOption.fromJson(
filter.map((key, value) => MapEntry(key.toString(), value)));
args[0]!.$value,
args[1]!.$value,
args[2]!.$value,
(args[3]!.$value as List).map((e) {
if (e is $Value) {
final value = e.$reified;
if (value is Map) {
Map<String, dynamic> map = {};
map = value.map((key, value) => MapEntry(key.toString(), value));
if (map['type'] == 'SelectOption') {
final filter = map['filter'] as Map;
return SelectFilterOption.fromJson(filter
.map((key, value) => MapEntry(key.toString(), value)));
}
}
return value;
}
return value;
}
return e;
}).toList(),
));
return e;
}).toList(),
null));
}
@override
@ -188,6 +192,12 @@ class $SelectFilter implements SelectFilter, $Instance {
@override
int get state => $value.state;
@override
String? get typeName => $value.typeName;
@override
set typeName(String? typeName) {}
@override
List<dynamic> get values => $value.values;
@ -202,6 +212,11 @@ class $SelectFilter implements SelectFilter, $Instance {
@override
set values(List<dynamic> values) {}
@override
Map<String, dynamic> toJson() {
throw UnimplementedError();
}
}
class $SelectFilterOption implements SelectFilterOption, $Instance {
@ -230,7 +245,7 @@ class $SelectFilterOption implements SelectFilterOption, $Instance {
static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
return $SelectFilterOption
.wrap(SelectFilterOption(args[0]!.$value, args[1]!.$value));
.wrap(SelectFilterOption(args[0]!.$value, args[1]!.$value, null));
}
@override
@ -276,11 +291,22 @@ class $SelectFilterOption implements SelectFilterOption, $Instance {
@override
String get name => $value.name;
@override
String? get typeName => $value.typeName;
@override
set typeName(String? typeName) {}
@override
set name(String name) {}
@override
set value(String value) {}
@override
Map<String, dynamic> toJson() {
throw UnimplementedError();
}
}
class $SeparatorFilter implements SeparatorFilter, $Instance {
@ -304,7 +330,8 @@ class $SeparatorFilter implements SeparatorFilter, $Instance {
wrap: true);
static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
return $SeparatorFilter.wrap(SeparatorFilter(type: args[0]?.$value ?? ''));
return $SeparatorFilter
.wrap(SeparatorFilter(null, type: args[0]?.$value ?? ''));
}
@override
@ -342,8 +369,19 @@ class $SeparatorFilter implements SeparatorFilter, $Instance {
@override
String? get type => $value.type ?? '';
@override
String? get typeName => $value.typeName;
@override
set typeName(String? typeName) {}
@override
set type(String? type) {}
@override
Map<String, dynamic> toJson() {
throw UnimplementedError();
}
}
class $HeaderFilter implements HeaderFilter, $Instance {
@ -372,7 +410,7 @@ class $HeaderFilter implements HeaderFilter, $Instance {
static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
return $HeaderFilter
.wrap(HeaderFilter(args[0]!.$value, type: args[1]?.$value ?? ''));
.wrap(HeaderFilter(args[0]!.$value, null, type: args[1]?.$value ?? ''));
}
@override
@ -423,6 +461,17 @@ class $HeaderFilter implements HeaderFilter, $Instance {
@override
String? get type => $value.type ?? '';
@override
String? get typeName => $value.typeName;
@override
set typeName(String? typeName) {}
@override
Map<String, dynamic> toJson() {
throw UnimplementedError();
}
}
class $TextFilter implements TextFilter, $Instance {
@ -450,7 +499,7 @@ class $TextFilter implements TextFilter, $Instance {
wrap: true);
static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
return $TextFilter.wrap(TextFilter(args[0]!.$value, args[1]!.$value));
return $TextFilter.wrap(TextFilter(args[0]!.$value, args[1]!.$value, null));
}
@override
@ -502,6 +551,12 @@ class $TextFilter implements TextFilter, $Instance {
@override
String get state => $value.state;
@override
String? get typeName => $value.typeName;
@override
set typeName(String? typeName) {}
@override
set type(String? type) {}
@ -510,6 +565,11 @@ class $TextFilter implements TextFilter, $Instance {
@override
set state(String state) {}
@override
Map<String, dynamic> toJson() {
throw UnimplementedError();
}
}
class $SortFilter implements SortFilter, $Instance {
@ -550,13 +610,14 @@ class $SortFilter implements SortFilter, $Instance {
static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
return $SortFilter.wrap(SortFilter(
args[0]!.$value,
args[1]!.$value,
args[2]!.$value,
(args[3]!.$value as List)
.map((e) => SelectFilterOption(e.$reified.name, e.$reified.value))
.toList(),
));
args[0]!.$value,
args[1]!.$value,
args[2]!.$value,
(args[3]!.$value as List)
.map((e) =>
SelectFilterOption(e.$reified.name, e.$reified.value, null))
.toList(),
null));
}
@override
@ -615,6 +676,12 @@ class $SortFilter implements SortFilter, $Instance {
@override
List<dynamic> get values => $value.values;
@override
String? get typeName => $value.typeName;
@override
set typeName(String? typeName) {}
@override
set type(String? type) {}
@ -626,6 +693,11 @@ class $SortFilter implements SortFilter, $Instance {
@override
set values(List<dynamic> values) {}
@override
Map<String, dynamic> toJson() {
throw UnimplementedError();
}
}
class $SortState implements SortState, $Instance {
@ -655,7 +727,7 @@ class $SortState implements SortState, $Instance {
wrap: true);
static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
return $SortState.wrap(SortState(args[0]!.$value, args[1]!.$value));
return $SortState.wrap(SortState(args[0]!.$value, args[1]!.$value, null));
}
@override
@ -701,11 +773,22 @@ class $SortState implements SortState, $Instance {
@override
bool get ascending => $value.ascending;
@override
String? get typeName => $value.typeName;
@override
set typeName(String? typeName) {}
@override
set ascending(bool ascending) {}
@override
set index(int index) {}
@override
Map<String, dynamic> toJson() {
throw UnimplementedError();
}
}
class $TriStateFilter implements TriStateFilter, $Instance {
@ -744,7 +827,7 @@ class $TriStateFilter implements TriStateFilter, $Instance {
static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
return $TriStateFilter.wrap(TriStateFilter(
args[2]?.$value ?? '', args[0]!.$value, args[1]!.$value,
args[2]?.$value ?? '', args[0]!.$value, args[1]!.$value, null,
state: args[3]?.$value ?? 0));
}
@ -808,6 +891,12 @@ class $TriStateFilter implements TriStateFilter, $Instance {
@override
String get value => $value.value;
@override
String? get typeName => $value.typeName;
@override
set typeName(String? typeName) {}
@override
set state(int state) {}
@ -816,6 +905,11 @@ class $TriStateFilter implements TriStateFilter, $Instance {
@override
set value(String value) {}
@override
Map<String, dynamic> toJson() {
throw UnimplementedError();
}
}
class $GroupFilter implements GroupFilter, $Instance {
@ -874,7 +968,8 @@ class $GroupFilter implements GroupFilter, $Instance {
return value;
}
return e;
}).toList()));
}).toList(),
null));
}
@override
@ -927,6 +1022,12 @@ class $GroupFilter implements GroupFilter, $Instance {
@override
List<dynamic> get state => $value.state;
@override
String? get typeName => $value.typeName;
@override
set typeName(String? typeName) {}
@override
set type(String? type) {}
@ -935,6 +1036,11 @@ class $GroupFilter implements GroupFilter, $Instance {
@override
set state(List<dynamic> state) {}
@override
Map<String, dynamic> toJson() {
throw UnimplementedError();
}
}
class $CheckBoxFilter implements CheckBoxFilter, $Instance {
@ -973,7 +1079,7 @@ class $CheckBoxFilter implements CheckBoxFilter, $Instance {
static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
return $CheckBoxFilter.wrap(CheckBoxFilter(
args[2]?.$value ?? '', args[0]!.$value, args[1]!.$value,
args[2]?.$value ?? '', args[0]!.$value, args[1]!.$value, null,
state: args[3]?.$value ?? false));
}
@ -1037,6 +1143,12 @@ class $CheckBoxFilter implements CheckBoxFilter, $Instance {
@override
String get value => $value.value;
@override
String? get typeName => $value.typeName;
@override
set typeName(String? typeName) {}
@override
set type(String? type) {}
@ -1045,4 +1157,9 @@ class $CheckBoxFilter implements CheckBoxFilter, $Instance {
@override
set value(String value) {}
@override
Map<String, dynamic> toJson() {
throw UnimplementedError();
}
}

View file

@ -1,12 +1,11 @@
import 'dart:convert';
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:flutter/foundation.dart';
import 'package:http_interceptor/http_interceptor.dart';
import 'package:mangayomi/eval/bridge/m_source.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/services/http/interceptor.dart';
import 'package:mangayomi/eval/dart/bridge/m_source.dart';
import 'package:mangayomi/eval/dart/model/m_bridge.dart';
import 'package:mangayomi/services/http/m_client.dart';
/// dart_eval wrapper for [InterceptedClient]
class $Client implements $Instance {
@ -218,7 +217,7 @@ class $Client implements $Instance {
: (jsonDecode(args[1]!.$value) as Map)
.map((key, value) => MapEntry(key.toString(), value));
return $Client.wrap(
MInterceptor.init(source: args[0]?.$value, reqcopyWith: reqcopyWith),
MClient.init(source: args[0]?.$value, reqcopyWith: reqcopyWith),
);
}

View file

@ -1,7 +1,6 @@
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:mangayomi/eval/model/m_chapter.dart';
import 'package:mangayomi/eval/dart/model/m_chapter.dart';
class $MChapter implements MChapter, $Instance {
$MChapter.wrap(this.$value) : _superclass = $Object($value);
@ -15,26 +14,14 @@ class $MChapter implements MChapter, $Instance {
returns: BridgeTypeAnnotation($type),
params: [],
namedParams: [
BridgeParameter(
'name',
BridgeTypeAnnotation(
BridgeTypeRef(CoreTypes.string)),
false),
BridgeParameter(
'url',
BridgeTypeAnnotation(
BridgeTypeRef(CoreTypes.string)),
false),
BridgeParameter(
'dateUpload',
BridgeTypeAnnotation(
BridgeTypeRef(CoreTypes.string)),
false),
BridgeParameter(
'scanlator',
BridgeTypeAnnotation(
BridgeTypeRef(CoreTypes.string)),
true),
BridgeParameter('name',
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), false),
BridgeParameter('url',
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), false),
BridgeParameter('dateUpload',
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), false),
BridgeParameter('scanlator',
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), true),
]))
},
// Specify class fields
@ -125,4 +112,12 @@ class $MChapter implements MChapter, $Instance {
@override
set scanlator(String? scanlator) {}
@override
Map<String, dynamic> toJson() => {
'name': name,
'url': url,
'dateUpload': dateUpload,
'scanlator': scanlator
};
}

View file

@ -1,11 +1,11 @@
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:mangayomi/eval/bridge/m_chapter.dart';
import 'package:mangayomi/eval/bridge/m_status.dart';
import 'package:mangayomi/eval/model/m_chapter.dart';
import 'package:mangayomi/eval/model/m_manga.dart';
import 'package:mangayomi/eval/dart/bridge/m_chapter.dart';
import 'package:mangayomi/eval/dart/bridge/m_status.dart';
import 'package:mangayomi/eval/dart/model/m_chapter.dart';
import 'package:mangayomi/eval/dart/model/m_manga.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/utils/extensions/string_extensions.dart';
class $MManga implements MManga, $Instance {
$MManga.wrap(this.$value) : _superclass = $Object($value);
@ -190,4 +190,17 @@ class $MManga implements MManga, $Instance {
@override
set chapters(List<MChapter>? chapters) {}
@override
Map<String, dynamic> toJson() => {
'name': name,
'link': link,
'imageUrl': imageUrl,
'description': description,
'author': author,
'artist': artist,
'status': status.toString().substringAfter("."),
'genre': genre,
'chapters': chapters?.map((e) => e.toJson()).toList()
};
}

View file

@ -1,9 +1,8 @@
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:mangayomi/eval/bridge/m_manga.dart';
import 'package:mangayomi/eval/model/m_manga.dart';
import 'package:mangayomi/eval/model/m_pages.dart';
import 'package:mangayomi/eval/dart/bridge/m_manga.dart';
import 'package:mangayomi/eval/dart/model/m_manga.dart';
import 'package:mangayomi/eval/dart/model/m_pages.dart';
class $MPages implements MPages, $Instance {
$MPages.wrap(this.$value) : _superclass = $Object($value);
@ -84,4 +83,10 @@ class $MPages implements MPages, $Instance {
@override
set list(List<MManga> list) {}
@override
Map<String, dynamic> toJson() => {
'list': list.map((v) => v.toJson()).toList(),
'hasNextPage': hasNextPage,
};
}

View file

@ -1,22 +1,22 @@
import 'dart:convert';
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:mangayomi/eval/bridge/document.dart';
import 'package:mangayomi/eval/bridge/filter.dart';
import 'package:mangayomi/eval/bridge/m_manga.dart';
import 'package:mangayomi/eval/bridge/m_pages.dart';
import 'package:mangayomi/eval/bridge/m_status.dart';
import 'package:mangayomi/eval/bridge/m_track.dart';
import 'package:mangayomi/eval/bridge/m_video.dart';
import 'package:mangayomi/eval/model/filter.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/eval/model/m_pages.dart';
import 'package:mangayomi/eval/model/m_manga.dart';
import 'package:mangayomi/eval/model/m_provider.dart';
import 'package:flutter_qjs/flutter_qjs.dart';
import 'package:mangayomi/eval/dart/bridge/document.dart';
import 'package:mangayomi/eval/dart/bridge/filter.dart';
import 'package:mangayomi/eval/dart/bridge/m_manga.dart';
import 'package:mangayomi/eval/dart/bridge/m_pages.dart';
import 'package:mangayomi/eval/dart/bridge/m_status.dart';
import 'package:mangayomi/eval/dart/bridge/m_track.dart';
import 'package:mangayomi/eval/dart/bridge/m_video.dart';
import 'package:mangayomi/eval/dart/model/filter.dart';
import 'package:mangayomi/eval/dart/model/m_bridge.dart';
import 'package:mangayomi/eval/dart/model/m_pages.dart';
import 'package:mangayomi/eval/dart/model/m_manga.dart';
import 'package:mangayomi/eval/dart/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/boa_js.dart';
import 'package:mangayomi/utils/log/log.dart';
class $MProvider extends MProvider with $Bridge<MProvider> {
static $MProvider $construct(
@ -737,6 +737,18 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
],
namedParams: []),
isStatic: true),
'print': BridgeMethodDef(
BridgeFunctionDef(
returns:
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.voidType)),
params: [
BridgeParameter(
'object',
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.dynamic)),
false),
],
namedParams: []),
isStatic: true),
},
bridge: true);
@ -747,9 +759,15 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
@override
$Value? $bridgeGet(String identifier) {
return switch (identifier) {
'print' => $Function((_, __, List<$Value?> args) {
Logger.add(LoggerLevel.warning, "${args[0]!.$reified}");
return null;
}),
'evalJs' => $Function((_, __, List<$Value?> args) {
return $Future
.wrap(evalJs(args[0]!.$reified).then((value) => $String(value)));
final runtime = getJavascriptRuntime();
return $Future.wrap(runtime
.evaluateAsync(args[0]!.$reified)
.then((value) => $String(value.stringResult)));
}),
'getUrlWithoutDomain' => $Function((_, __, List<$Value?> args) {
final uri = Uri.parse(args[0]!.$value.replaceAll(' ', '%20'));
@ -975,10 +993,14 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
@override
Future<List<String>> getPageList(String url) async {
final res = await $_invoke('getPageList', [$String(url)]);
List<dynamic> list = [];
if (res is $List) {
return res.$reified.map((e) => e as String).toList();
list = res.$reified;
} else {
list = res;
}
return res;
return list.map((e) => (e is $Value ? e.$reified : e) as String).toList();
}
@override
@ -1002,10 +1024,14 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
@override
Future<List<Video>> getVideoList(String url) async {
final res = await $_invoke('getVideoList', [$String(url)]);
List<dynamic> list = [];
if (res is $List) {
return res.$reified.map((e) => e as Video).toList();
list = res.$reified;
} else {
list = res;
}
return res;
return list.map((e) => (e is $Value ? e.$reified : e) as Video).toList();
}
$MVideo _toMVideo(Video e) =>
@ -1025,8 +1051,8 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
_toValueList(List filters) {
return (filters).map((e) {
if (e is SelectFilter) {
return $SelectFilter.wrap(
SelectFilter(e.type, e.name, e.state, _toValueList(e.values)));
return $SelectFilter.wrap(SelectFilter(
e.type, e.name, e.state, _toValueList(e.values), e.typeName));
} else if (e is TextFilter) {
return $TextFilter.wrap(e);
} else if (e is HeaderFilter) {
@ -1038,8 +1064,8 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
} else if (e is SeparatorFilter) {
return $SeparatorFilter.wrap(e);
} else if (e is SortFilter) {
return $SortFilter
.wrap(SortFilter(e.type, e.name, e.state, _toValueList(e.values)));
return $SortFilter.wrap(SortFilter(
e.type, e.name, e.state, _toValueList(e.values), e.typeName));
} else if (e is SortState) {
return $SortState.wrap(e);
} else if (e is CheckBoxFilter) {
@ -1047,8 +1073,8 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
} else if (e is SelectFilterOption) {
return $SelectFilterOption.wrap(e);
} else if (e is GroupFilter) {
return $GroupFilter
.wrap(GroupFilter(e.type, e.name, _toValueList(e.state)));
return $GroupFilter.wrap(
GroupFilter(e.type, e.name, _toValueList(e.state), e.typeName));
}
return e;
}).toList();

View file

@ -1,7 +1,6 @@
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:mangayomi/eval/model/m_source.dart';
import 'package:mangayomi/eval/dart/model/m_source.dart';
class $MSource implements MSource, $Instance {
$MSource.wrap(this.$value) : _superclass = $Object($value);
@ -169,4 +168,18 @@ class $MSource implements MSource, $Instance {
@override
set additionalParams(String? additionalParams) {}
@override
Map<String, dynamic> toJson() => {
'apiUrl': apiUrl,
'baseUrl': baseUrl,
'dateFormat': dateFormat,
'dateFormatLocale': dateFormatLocale,
'hasCloudflare': hasCloudflare,
'id': id,
'isFullData': isFullData,
'lang': lang,
'name': name,
'additionalParams': additionalParams
};
}

View file

@ -1,4 +1,3 @@
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:mangayomi/models/manga.dart';

View file

@ -1,4 +1,3 @@
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:mangayomi/models/video.dart';
@ -76,4 +75,9 @@ class $MTrack implements Track, $Instance {
@override
set label(String? label) {}
@override
Map<String, dynamic> toJson() {
throw UnimplementedError();
}
}

View file

@ -1,7 +1,6 @@
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:mangayomi/eval/bridge/m_track.dart';
import 'package:mangayomi/eval/dart/bridge/m_track.dart';
import 'package:mangayomi/models/video.dart';
class $MVideo implements Video, $Instance {
@ -139,4 +138,9 @@ class $MVideo implements Video, $Instance {
@override
set audios(List? audios) {}
@override
Map<String, dynamic> toJson() {
throw UnimplementedError();
}
}

View file

@ -1,8 +1,7 @@
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/source_preference.dart';
import 'package:mangayomi/eval/dart/model/source_preference.dart';
class $CheckBoxPreference implements SourcePreference, $Instance {
$CheckBoxPreference.wrap(this.$value) : _superclass = $Object($value);

View file

@ -1,6 +1,6 @@
import 'dart:typed_data';
import 'package:dart_eval/dart_eval.dart';
import 'package:mangayomi/eval/plugin.dart';
import 'package:mangayomi/eval/dart/plugin.dart';
Uint8List compilerEval(String code) {
late Compiler compiler = Compiler();

View file

@ -1,5 +1,5 @@
import 'package:html/dom.dart';
import 'package:mangayomi/eval/model/element.dart';
import 'package:mangayomi/eval/dart/model/element.dart';
import 'package:mangayomi/utils/extensions/dom_extensions.dart';
class MDocument {
@ -55,4 +55,8 @@ class MDocument {
MElement? selectFirst(String selector) {
return MElement(_document?.selectFirst(selector));
}
String? attr(String attr) {
return _document?.attr(attr);
}
}

View file

@ -0,0 +1,237 @@
import 'package:mangayomi/eval/javascript/http.dart';
class FilterList {
List<dynamic> filters;
FilterList(this.filters);
factory FilterList.fromJson(Map<String, dynamic> json) {
return FilterList(fromJsonFilterValuestoList(json['filters']));
}
Map<String, dynamic> toJson() => {'filters': filterValuesListToJson(filters)};
}
class SelectFilter {
String? type;
String name;
int state;
List<dynamic> values;
String? typeName;
SelectFilter(this.type, this.name, this.state, this.values, this.typeName);
factory SelectFilter.fromJson(Map<String, dynamic> json) {
return SelectFilter(json['type'], json['name'], json['state'] ?? 0,
fromJsonFilterValuestoList(json['values']), json['type_name']);
}
Map<String, dynamic> toJson() => {
'type': type,
'name': name,
'values': filterValuesListToJson(values),
'type_name': typeName
};
}
class SelectFilterOption {
String name;
String value;
String? typeName;
SelectFilterOption(this.name, this.value, this.typeName);
factory SelectFilterOption.fromJson(Map<String, dynamic> json) {
return SelectFilterOption(json['name'], json['value'], json['type_name']);
}
Map<String, dynamic> toJson() =>
{'value': value, 'name': name, 'type_name': typeName};
}
class SeparatorFilter {
String? type;
String? typeName;
SeparatorFilter(this.typeName, {this.type = ''});
factory SeparatorFilter.fromJson(Map<String, dynamic> json) {
return SeparatorFilter(type: json['type'], json['type_name']);
}
Map<String, dynamic> toJson() => {'type': type, 'type_name': typeName};
}
class HeaderFilter {
String? type;
String name;
String? typeName;
HeaderFilter(this.name, this.typeName, {this.type = ''});
factory HeaderFilter.fromJson(Map<String, dynamic> json) {
return HeaderFilter(json['name'], json['type_name'], type: json['value']);
}
Map<String, dynamic> toJson() =>
{'type': type, 'name': name, 'type_name': typeName};
}
class TextFilter {
String? type;
String name;
String state;
String? typeName;
TextFilter(this.type, this.name, this.typeName, {this.state = ""});
factory TextFilter.fromJson(Map<String, dynamic> json) {
return TextFilter(json['type'], json['name'], json['type_name'],
state: json['state'] ?? "");
}
Map<String, dynamic> toJson() =>
{'type': type, 'name': name, 'state': state, 'type_name': typeName};
}
class SortFilter {
String? type;
String name;
SortState state;
List<dynamic> values;
String? typeName;
SortFilter(this.type, this.name, this.state, this.values, this.typeName);
factory SortFilter.fromJson(Map<String, dynamic> json) {
return SortFilter(
json['type'],
json['name'],
json['state'] == null
? SortState(0, false, "")
: SortState.fromJson(json['state']),
fromJsonFilterValuestoList(json['values']),
json['type_name']);
}
Map<String, dynamic> toJson() => {
'type': type,
'name': name,
'state': state,
'values': filterValuesListToJson(values),
'type_name': typeName
};
}
class SortState {
int index;
bool ascending;
String? typeName;
SortState(this.index, this.ascending, this.typeName);
factory SortState.fromJson(Map<String, dynamic> json) {
return SortState(json['index'], json['ascending'], json['type_name']);
}
Map<String, dynamic> toJson() =>
{'index': index, 'ascending': ascending, 'type_name': typeName};
}
class TriStateFilter {
String? type;
String name;
String value;
int state;
String? typeName;
factory TriStateFilter.fromJson(Map<String, dynamic> json) {
return TriStateFilter(
json['type'], json['name'], json['value'], json['type_name'],
state: json['state'] ?? 0);
}
TriStateFilter(this.type, this.name, this.value, this.typeName,
{this.state = 0});
Map<String, dynamic> toJson() => {
'type': type,
'name': name,
'value': value,
'state': state,
'type_name': typeName
};
}
class GroupFilter {
String? type;
String name;
List<dynamic> state;
String? typeName;
GroupFilter(this.type, this.name, this.state, this.typeName);
factory GroupFilter.fromJson(Map<String, dynamic> json) {
return GroupFilter(json['type'], json['name'],
fromJsonFilterValuestoList(json['state']), json['type_name']);
}
Map<String, dynamic> toJson() => {
'type': type,
'name': name,
'state': filterValuesListToJson(state),
'type_name': typeName
};
}
class CheckBoxFilter {
String? type;
String name;
String value;
bool state;
String? typeName;
CheckBoxFilter(this.type, this.name, this.value, this.typeName,
{this.state = false});
factory CheckBoxFilter.fromJson(Map<String, dynamic> json) {
return CheckBoxFilter(
json['type'], json['name'], json['value'], json['type_name'],
state: json['state'] ?? false);
}
Map<String, dynamic> toJson() => {
'type': type,
'name': name,
'value': value,
'state': state,
'type_name': typeName
};
}
List<dynamic> fromJsonFilterValuestoList(List list) {
return list.map((e) {
final map = (e as Map).toMapStringDynamic!;
if (map['type_name'] == 'TriState') {
return TriStateFilter.fromJson(map.toMapStringDynamic!);
} else if (map['type_name'] == 'CheckBox') {
return CheckBoxFilter.fromJson(map.toMapStringDynamic!);
} else if (map['type_name'] == 'SelectOption') {
return SelectFilterOption.fromJson(map.toMapStringDynamic!);
} else if (map['type_name'] == 'SelectFilter') {
return SelectFilter.fromJson(map.toMapStringDynamic!);
} else if (map['type_name'] == 'SeparatorFilter') {
return SeparatorFilter.fromJson(map.toMapStringDynamic!);
} else if (map['type_name'] == 'HeaderFilter') {
return HeaderFilter.fromJson(map.toMapStringDynamic!);
} else if (map['type_name'] == 'TextFilter') {
return TextFilter.fromJson(map.toMapStringDynamic!);
} else if (map['type_name'] == 'SortFilter') {
return SortFilter.fromJson(map.toMapStringDynamic!);
} else if (map['type_name'] == 'SortState') {
return SortState.fromJson(map.toMapStringDynamic!);
} else if (map['type_name'] == 'GroupFilter') {
return GroupFilter.fromJson(map.toMapStringDynamic!);
}
}).toList();
}
List<Map<String, dynamic>?> filterValuesListToJson(List<dynamic> values) {
return values.map((e) {
if (e is SelectFilter) {
return e.toJson();
} else if (e is SelectFilterOption) {
return e.toJson();
} else if (e is SeparatorFilter) {
return e.toJson();
} else if (e is HeaderFilter) {
return e.toJson();
} else if (e is TextFilter) {
return e.toJson();
} else if (e is SortFilter) {
return e.toJson();
} else if (e is SortState) {
return e.toJson();
} else if (e is TriStateFilter) {
return e.toJson();
} else if (e is GroupFilter) {
return e.toJson();
}
return (e.toJson() as Map<String, dynamic>?);
}).toList();
}

View file

@ -8,7 +8,7 @@ import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart';
import 'package:js_packer/js_packer.dart';
import 'package:json_path/json_path.dart';
import 'package:mangayomi/eval/model/document.dart';
import 'package:mangayomi/eval/dart/model/document.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/services/anime_extractors/dood_extractor.dart';
import 'package:mangayomi/services/anime_extractors/filemoon.dart';

View file

@ -0,0 +1,23 @@
class MChapter {
String? name;
String? url;
String? dateUpload;
String? scanlator;
MChapter({this.name, this.url, this.dateUpload, this.scanlator});
factory MChapter.fromJson(Map<String, dynamic> json) {
return MChapter(
name: json['name'],
url: json['url'],
dateUpload: json['dateUpload'],
scanlator: json['scanlator']);
}
Map<String, dynamic> toJson() => {
'name': name,
'url': url,
'dateUpload': dateUpload,
'scanlator': scanlator
};
}

View file

@ -0,0 +1,74 @@
import 'package:mangayomi/eval/dart/model/m_chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/utils/extensions/string_extensions.dart';
class MManga {
String? name;
String? link;
String? imageUrl;
String? description;
String? author;
String? artist;
Status? status;
List<String>? genre;
List<MChapter>? chapters;
MManga(
{this.author,
this.artist,
this.genre,
this.imageUrl,
this.link,
this.name,
this.status = Status.unknown,
this.description,
this.chapters});
factory MManga.fromJson(Map<String, dynamic> json) {
return MManga(
name: json['name'],
link: json['link'],
imageUrl: json['imageUrl'],
description: json['description'],
author: json['author'],
artist: json['artist'],
status: switch (json['status'] as int?) {
0 => Status.ongoing,
1 => Status.completed,
2 => Status.onHiatus,
3 => Status.canceled,
4 => Status.publishingFinished,
_ => Status.unknown,
},
genre:
(json['genre'] as List?)?.map((e) => e.toString()).toList() ?? [],
chapters: json['chapters'] != null
? (json['chapters'] as List)
.map((e) => MChapter.fromJson(e))
.toList()
: json['episodes'] != null
? (json['episodes'] as List)
.map((e) => MChapter.fromJson(e))
.toList()
: []);
}
Map<String, dynamic> toJson() => {
'name': name,
'link': link,
'imageUrl': imageUrl,
'description': description,
'author': author,
'artist': artist,
'status': status.toString().substringAfter("."),
'genre': genre,
'chapters': chapters!.map((e) => e.toJson()).toList()
};
}

View file

@ -0,0 +1,20 @@
import 'package:mangayomi/eval/dart/model/m_manga.dart';
class MPages {
List<MManga> list;
bool hasNextPage;
MPages({required this.list, this.hasNextPage = false});
factory MPages.fromJson(Map<String, dynamic> json) {
return MPages(
list: json['list'] != null
? (json['list'] as List).map((e) => MManga.fromJson(e)).toList()
: [],
hasNextPage: json['hasNextPage']);
}
Map<String, dynamic> toJson() => {
'list': list.map((v) => v.toJson()).toList(),
'hasNextPage': hasNextPage,
};
}

View file

@ -1,6 +1,6 @@
import 'package:mangayomi/eval/model/filter.dart';
import 'package:mangayomi/eval/model/m_pages.dart';
import 'package:mangayomi/eval/model/m_manga.dart';
import 'package:mangayomi/eval/dart/model/filter.dart';
import 'package:mangayomi/eval/dart/model/m_pages.dart';
import 'package:mangayomi/eval/dart/model/m_manga.dart';
import 'package:mangayomi/models/video.dart';
abstract class MProvider {

View file

@ -30,4 +30,17 @@ class MSource {
this.dateFormatLocale,
this.apiUrl,
this.additionalParams});
Map<String, dynamic> toJson() => {
'apiUrl': apiUrl,
'baseUrl': baseUrl,
'dateFormat': dateFormat,
'dateFormatLocale': dateFormatLocale,
'hasCloudflare': hasCloudflare,
'id': id,
'isFullData': isFullData,
'lang': lang,
'name': name,
'additionalParams': additionalParams
};
}

View file

@ -40,7 +40,7 @@ class SourcePreference {
factory SourcePreference.fromJson(Map<String, dynamic> json) {
return SourcePreference(
id: json['id'],
id: json['id'] ?? Isar.autoIncrement,
sourceId: json['sourceId'],
key: json['key'],
checkBoxPreference: json['checkBoxPreference'] != null

View file

@ -1,18 +1,17 @@
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:mangayomi/eval/bridge/document.dart';
import 'package:mangayomi/eval/bridge/element.dart';
import 'package:mangayomi/eval/bridge/http.dart';
import 'package:mangayomi/eval/bridge/m_chapter.dart';
import 'package:mangayomi/eval/bridge/filter.dart';
import 'package:mangayomi/eval/bridge/m_pages.dart';
import 'package:mangayomi/eval/bridge/m_status.dart';
import 'package:mangayomi/eval/bridge/m_provider.dart';
import 'package:mangayomi/eval/bridge/m_manga.dart';
import 'package:mangayomi/eval/bridge/m_source.dart';
import 'package:mangayomi/eval/bridge/m_track.dart';
import 'package:mangayomi/eval/bridge/m_video.dart';
import 'package:mangayomi/eval/bridge/source_preference.dart';
import 'package:mangayomi/eval/dart/bridge/document.dart';
import 'package:mangayomi/eval/dart/bridge/element.dart';
import 'package:mangayomi/eval/dart/bridge/http.dart';
import 'package:mangayomi/eval/dart/bridge/m_chapter.dart';
import 'package:mangayomi/eval/dart/bridge/filter.dart';
import 'package:mangayomi/eval/dart/bridge/m_pages.dart';
import 'package:mangayomi/eval/dart/bridge/m_status.dart';
import 'package:mangayomi/eval/dart/bridge/m_provider.dart';
import 'package:mangayomi/eval/dart/bridge/m_manga.dart';
import 'package:mangayomi/eval/dart/bridge/m_source.dart';
import 'package:mangayomi/eval/dart/bridge/m_track.dart';
import 'package:mangayomi/eval/dart/bridge/m_video.dart';
import 'package:mangayomi/eval/dart/bridge/source_preference.dart';
class MEvalPlugin extends EvalPlugin {
@override

View file

@ -1,6 +1,6 @@
import 'dart:typed_data';
import 'package:dart_eval/dart_eval.dart';
import 'package:mangayomi/eval/plugin.dart';
import 'package:mangayomi/eval/dart/plugin.dart';
Runtime runtimeEval(Uint8List bytecode) {
final runtime = Runtime(bytecode.buffer.asByteData());

View file

@ -0,0 +1,385 @@
import 'dart:convert';
import 'package:flutter_qjs/flutter_qjs.dart';
import 'package:html/dom.dart';
import 'package:html/parser.dart';
import 'package:mangayomi/utils/extensions/dom_extensions.dart';
class JsDomSelector {
late JavascriptRuntime runtime;
JsDomSelector(this.runtime);
final Map<int, Element?> _elements = {};
int _elementKey = 0;
init() {
runtime.onMessage('get_doc_element', (dynamic args) {
final input = args[0];
final type = args[1];
final doc = parse(input);
final element = switch (type) {
'body' => doc.body,
'documentElement' => doc.documentElement,
'head' => doc.head,
_ => doc.parent
};
_elementKey++;
_elements[_elementKey] = element;
return _elementKey;
});
runtime.onMessage('get_doc_string', (dynamic args) {
final input = args[0];
final type = args[1];
final doc = parse(input);
final res = switch (type) { 'text' => doc.text, _ => doc.outerHtml };
return res ?? "";
});
runtime.onMessage('get_element_string', (dynamic args) {
final type = args[0];
final key = args[1];
final element = _elements[key];
final res = switch (type) {
'text' => element?.text,
'innerHtml' => element?.innerHtml,
'outerHtml' => element?.outerHtml,
'className' => element?.className,
'localName' => element?.localName,
'namespaceUri' => element?.namespaceUri,
'getSrc' => element?.getSrc,
'getImg' => element?.getImg,
'getHref' => element?.getHref,
_ => element?.getDataSrc
};
return res ?? "";
});
runtime.onMessage('doc_select_first', (dynamic args) {
final input = args[0];
final selector = args[1];
_elementKey++;
_elements[_elementKey] = parse(input).selectFirst(selector);
return _elementKey;
});
runtime.onMessage('ele_selectFirst', (dynamic args) {
final selector = args[0];
final key = args[1];
_elementKey++;
_elements[_elementKey] = _elements[key]?.selectFirst(selector);
return _elementKey;
});
runtime.onMessage('ele_element_sibling', (dynamic args) {
final type = args[0];
final key = args[1];
final ele = _elements[key];
final element = switch (type) {
'nextElementSibling' => ele?.nextElementSibling,
_ => ele?.previousElementSibling
};
_elementKey++;
_elements[_elementKey] = element;
return _elementKey;
});
runtime.onMessage('ele_attr', (dynamic args) {
final attr = args[0];
final key = args[1];
return _elements[key]?.attr(attr) ?? "";
});
runtime.onMessage('doc_attr', (dynamic args) {
final input = args[0];
final attr = args[1];
return parse(input).attr(attr) ?? "";
});
runtime.onMessage('doc_xpath_first', (dynamic args) {
final input = args[0];
final xpath = args[1];
return parse(input).xpathFirst(xpath) ?? "";
});
runtime.onMessage('ele_xpathFirst', (dynamic args) {
final xpath = args[0];
final key = args[1];
return _elements[key]?.xpathFirst(xpath) ?? "";
});
runtime.onMessage('doc_xpath', (dynamic args) {
final input = args[0];
final xpath = args[1];
return jsonEncode(parse(input).xpath(xpath));
});
runtime.onMessage('ele_xpath', (dynamic args) {
final xpath = args[0];
final key = args[1];
return jsonEncode(_elements[key]?.xpath(xpath));
});
runtime.onMessage('doc_get_elements_by', (dynamic args) {
final input = args[0];
final type = args[1];
final name = args[2];
final doc = parse(input);
final elements = switch (type) {
'children' => doc.children,
'getElementsByTagName' => doc.getElementsByTagName(name),
_ => doc.getElementsByClassName(name)
};
List<int> elementKeys = [];
for (var element in elements) {
_elementKey++;
_elements[_elementKey] = element;
elementKeys.add(_elementKey);
}
return jsonEncode(elementKeys);
});
runtime.onMessage('ele_get_elements_by', (dynamic args) {
final type = args[0];
final name = args[1];
final key = args[2];
final element = _elements[key];
final elements = switch (type) {
'children' => element?.children,
'getElementsByTagName' => element?.getElementsByTagName(name),
_ => element?.getElementsByClassName(name)
};
List<int> elementKeys = [];
for (var element in elements ?? []) {
_elementKey++;
_elements[_elementKey] = element;
elementKeys.add(_elementKey);
}
return jsonEncode(elementKeys);
});
runtime.onMessage('doc_get_element_by_id', (dynamic args) {
final input = args[0];
final id = args[1];
_elementKey++;
_elements[_elementKey] = parse(input).getElementById(id);
return _elementKey;
});
runtime.onMessage('doc_select', (dynamic args) {
final input = args[0];
final selector = args[1];
final elements = parse(input).select(selector);
List<int> elementKeys = [];
for (var element in elements ?? []) {
_elementKey++;
_elements[_elementKey] = element;
elementKeys.add(_elementKey);
}
return jsonEncode(elementKeys);
});
runtime.onMessage('ele_select', (dynamic args) {
final selector = args[0];
final key = args[1];
final elements = _elements[key]?.select(selector);
List<int> elementKeys = [];
for (var element in elements ?? []) {
_elementKey++;
_elements[_elementKey] = element;
elementKeys.add(_elementKey);
}
return jsonEncode(elementKeys);
});
runtime.evaluate('''
class Document {
constructor(html) {
this.html = html;
}
getElement(type) {
const key = sendMessage(
"get_doc_element",
JSON.stringify([this.html, type])
);
return new Element(key);
}
get body() {
return this.getElement('body');
}
get documentElement() {
return this.getElement('documentElement');
}
get head() {
return this.getElement('head');
}
get parent() {
return this.getElement('parent');
}
getString(type) {
return sendMessage(
"get_doc_string",
JSON.stringify([this.html, type]));
}
get text() {
return this.getString('text');
}
get outerHtml() {
return this.getString('outerHtml');
}
selectFirst(selector) {
const key = sendMessage(
"doc_select_first",
JSON.stringify([this.html, selector])
);
return new Element(key);
}
select(selector) {
let elements = [];
JSON.parse(
sendMessage("doc_select", JSON.stringify([this.html, selector]))
).forEach((key) => {
elements.push(new Element(key));
});
return elements;
}
xpathFirst(xpath) {
return sendMessage(
"doc_xpath_first",
JSON.stringify([this.html, xpath])
);
}
xpath(xpath) {
return JSON.parse(sendMessage(
"doc_xpath",
JSON.stringify([this.html, xpath]))
);
}
getElementsListBy(type, name) {
name = name || '';
let elements = [];
JSON.parse(sendMessage(
"doc_get_elements_by",
JSON.stringify([this.html, type, name]))
).forEach((key) => {
elements.push(new Element(key));
});
return elements;
}
get children() {
return this.getElementsListBy('children');
}
getElementsByTagName(name) {
return this.getElementsListBy('getElementsByTagName', name);
}
getElementsByClassName(name) {
return this.getElementsListBy('getElementsByClassName', name);
}
getElementById(id) {
const key = sendMessage(
"doc_get_element_by_id",
JSON.stringify([this.html, id])
);
return new Element(key);
}
attr(attr) {
return sendMessage(
"doc_attr",
JSON.stringify([this.html, attr])
);
}
}
class Element {
constructor(key) {
this.key = key;
}
getString(type) {
return sendMessage(
"get_element_string",
JSON.stringify([type, this.key])
);
}
get text() {
return this.getString("text");
}
get outerHtml() {
return this.getString("outerHtml");
}
get innerHtml() {
return this.getString("innerHtml");
}
get className() {
return this.getString("className");
}
get localName() {
return this.getString("localName");
}
get namespaceUri() {
return this.getString("namespaceUri");
}
get getSrc() {
return this.getString("getSrc");
}
get getImg() {
return this.getString("getImg");
}
get getHref() {
return this.getString("getHref");
}
get getDataSrc() {
return this.getString("getDataSrc");
}
getElementSibling(type) {
const key = sendMessage(
"ele_element_sibling",
JSON.stringify([type, this.key])
);
return new Element(key);
}
get previousElementSibling() {
return this.getElementSibling("previousElementSibling");
}
get nextElementSibling() {
return this.getElementSibling("nextElementSibling");
}
getElementsListBy(type, name) {
name = name || '';
let elements = [];
JSON.parse(sendMessage(
"ele_get_elements_by",
JSON.stringify([type, name, this.key]))
).forEach((key) => {
elements.push(new Element(key));
});
return elements;
}
get children() {
return this.getElementsListBy('children');
}
getElementsByTagName(name) {
return this.getElementsListBy('getElementsByTagName', name);
}
getElementsByClassName(name) {
return this.getElementsListBy('getElementsByClassName', name);
}
xpath(xpath) {
return JSON.parse(sendMessage(
"xpath",
JSON.stringify([xpath, this.key]))
);
}
attr(attr) {
return sendMessage(
"ele_attr",
JSON.stringify([attr, this.key])
);
}
xpathFirst(xpath) {
return sendMessage(
"xpathFirst",
JSON.stringify([xpath, this.key])
);
}
selectFirst(selector) {
const key = sendMessage(
"ele_selectFirst",
JSON.stringify([selector, this.key])
);
return new Element(key);
}
select(selector) {
let elements = [];
JSON.parse(
sendMessage("ele_select", JSON.stringify([selector, this.key]))
).forEach((key) => {
elements.push(new Element(key));
});
return elements;
}
}
''');
}
}

View file

@ -0,0 +1,172 @@
import 'dart:convert';
import 'package:flutter_qjs/flutter_qjs.dart';
import 'package:mangayomi/eval/dart/model/m_bridge.dart';
import 'package:mangayomi/models/video.dart';
class JsVideosExtractors {
late JavascriptRuntime runtime;
JsVideosExtractors(this.runtime);
init() {
runtime.onMessage('sibnetExtractor', (dynamic args) async {
return (await MBridge.sibnetExtractor(args[0], args[1] ?? ""))
.encodeToJson();
});
runtime.onMessage('myTvExtractor', (dynamic args) async {
return (await MBridge.myTvExtractor(args[0])).encodeToJson();
});
runtime.onMessage('okruExtractor', (dynamic args) async {
return (await MBridge.okruExtractor(args[0])).encodeToJson();
});
runtime.onMessage('voeExtractor', (dynamic args) async {
return (await MBridge.voeExtractor(args[0], args[1])).encodeToJson();
});
runtime.onMessage('vidBomExtractor', (dynamic args) async {
return (await MBridge.vidBomExtractor(args[0])).encodeToJson();
});
runtime.onMessage('streamlareExtractor', (dynamic args) async {
return (await MBridge.streamlareExtractor(args[0], args[1], args[2]))
.encodeToJson();
});
runtime.onMessage('sendVidExtractor', (dynamic args) async {
return (await MBridge.sendVidExtractor(args[0], args[1], args[2] ?? ""))
.encodeToJson();
});
runtime.onMessage('yourUploadExtractor', (dynamic args) async {
return (await MBridge.yourUploadExtractor(
args[0], args[1], args[2], args[3] ?? ""))
.encodeToJson();
});
runtime.onMessage('gogoCdnExtractor', (dynamic args) async {
return (await MBridge.gogoCdnExtractor(args[0])).encodeToJson();
});
runtime.onMessage('doodExtractor', (dynamic args) async {
return (await MBridge.doodExtractor(args[0], args[1])).encodeToJson();
});
runtime.onMessage('streamTapeExtractor', (dynamic args) async {
return (await MBridge.streamTapeExtractor(args[0], args[1]))
.encodeToJson();
});
runtime.onMessage('mp4UploadExtractor', (dynamic args) async {
return (await MBridge.mp4UploadExtractor(
args[0], args[1], args[2], args[3] ?? ""))
.encodeToJson();
});
runtime.onMessage('streamWishExtractor', (dynamic args) async {
return (await MBridge.streamWishExtractor(args[0], args[1] ?? ""))
.encodeToJson();
});
runtime.onMessage('filemoonExtractor', (dynamic args) async {
return (await MBridge.filemoonExtractor(
args[0], args[1] ?? "", args[2] ?? ""))
.encodeToJson();
});
runtime.evaluate('''
async function sibnetExtractor(url, prefix) {
const result = await sendMessage(
"sibnetExtractor",
JSON.stringify([url, prefix])
);
return JSON.parse(result);
}
async function myTvExtractor(url) {
const result = await sendMessage(
"myTvExtractor",
JSON.stringify([url])
);
return JSON.parse(result);
}
async function okruExtractor(url) {
const result = await sendMessage(
"okruExtractor",
JSON.stringify([url])
);
return JSON.parse(result);
}
async function voeExtractor(url, quality) {
const result = await sendMessage(
"voeExtractor",
JSON.stringify([url, quality])
);
return JSON.parse(result);
}
async function vidBomExtractor(url) {
const result = await sendMessage(
"vidBomExtractor",
JSON.stringify([url])
);
return JSON.parse(result);
}
async function streamlareExtractor(url, prefix, suffix) {
const result = await sendMessage(
"streamlareExtractor",
JSON.stringify([url, prefix, suffix])
);
return JSON.parse(result);
}
async function sendVidExtractor(url, headers, prefix) {
const result = await sendMessage(
"sendVidExtractor",
JSON.stringify([url, JSON.stringify(headers), prefix])
);
return JSON.parse(result);
}
async function yourUploadExtractor(url, headers, name, prefix) {
const result = await sendMessage(
"yourUploadExtractor",
JSON.stringify([url, JSON.stringify(headers), name, prefix])
);
return JSON.parse(result);
}
async function gogoCdnExtractor(url) {
const result = await sendMessage(
"gogoCdnExtractor",
JSON.stringify([url])
);
return JSON.parse(result);
}
async function doodExtractor(url, quality) {
const result = await sendMessage(
"doodExtractor",
JSON.stringify([url, quality])
);
return JSON.parse(result);
}
async function streamTapeExtractor(url, quality) {
const result = await sendMessage(
"streamTapeExtractor",
JSON.stringify([url, quality])
);
return JSON.parse(result);
}
async function mp4UploadExtractor(url, headers, prefix, suffix) {
const result = await sendMessage(
"mp4UploadExtractor",
JSON.stringify([url, JSON.stringify(headers), prefix, suffix])
);
return JSON.parse(result);
}
async function streamWishExtractor(url, prefix) {
const result = await sendMessage(
"streamWishExtractor",
JSON.stringify([url, prefix])
);
return JSON.parse(result);
}
async function filemoonExtractor(url, prefix, suffix) {
const result = await sendMessage(
"filemoonExtractor",
JSON.stringify([url, prefix, suffix])
);
return JSON.parse(result);
}
''');
}
}
extension ResponseExtexsion on List<Video> {
String encodeToJson() {
return jsonEncode(map((e) => e.toJson()).toList());
}
}

View file

@ -0,0 +1,134 @@
import 'dart:convert';
import 'package:flutter_qjs/flutter_qjs.dart';
import 'package:http_interceptor/http_interceptor.dart';
import 'package:mangayomi/services/http/m_client.dart';
class JsHttpClient {
late JavascriptRuntime runtime;
JsHttpClient(this.runtime);
init() {
runtime.onMessage('http_get', (dynamic args) async {
return jsonEncode((await MClient.init(
source: null,
reqcopyWith: (args[1] as Map?)?.toMapStringDynamic)
.get(Uri.parse(args[2]),
headers: (args[3] as Map?)?.toMapStringString))
.toJson());
});
runtime.onMessage('http_post', (dynamic args) async {
return jsonEncode((await MClient.init(
source: null,
reqcopyWith: (args[1] as Map?)?.toMapStringDynamic)
.post(Uri.parse(args[2]),
headers: (args[3] as Map?)?.toMapStringString, body: args[4]))
.toJson());
});
runtime.onMessage('http_put', (dynamic args) async {
return (await MClient.init(
source: null,
reqcopyWith: (args[1] as Map?)?.toMapStringDynamic)
.put(Uri.parse(args[2]),
headers: (args[3] as Map?)?.toMapStringString, body: args[4]))
.toJson();
});
runtime.onMessage('http_delete', (dynamic args) async {
return jsonEncode((await MClient.init(
source: null,
reqcopyWith: (args[1] as Map?)?.map(
(key, value) => MapEntry(key.toString(), value))).delete(
Uri.parse(args[0]),
headers: (args[1] as Map?)?.map(
(key, value) => MapEntry(key.toString(), value.toString())),
body: args[2]))
.toJson());
});
runtime.onMessage('http_patch', (dynamic args) async {
return jsonEncode((await MClient.init(
source: null,
reqcopyWith: (args[1] as Map?)?.toMapStringDynamic)
.patch(Uri.parse(args[2]),
headers: (args[3] as Map?)?.toMapStringString, body: args[4]))
.toJson());
});
runtime.evaluate('''
class Client {
constructor(reqcopyWith) {
this.reqcopyWith = reqcopyWith;
}
async get(url, headers) {
headers = headers;
const result = await sendMessage(
"http_get",
JSON.stringify([null, this.reqcopyWith, url, headers])
);
return JSON.parse(result);
}
async post(url, headers, body) {
headers = headers;
const result = await sendMessage(
"http_post",
JSON.stringify([null, this.reqcopyWith, url, headers, body])
);
return JSON.parse(result);
}
async put(url, headers, body) {
headers = headers;
const result = await sendMessage(
"http_post",
JSON.stringify([null, this.reqcopyWith, url, headers, body])
);
return JSON.parse(result);
}
async delete(url, headers, body) {
headers = headers;
const result = await sendMessage(
"http_post",
JSON.stringify([null, this.reqcopyWith, url, headers, body])
);
return JSON.parse(result);
}
async patch(url, headers, body) {
headers = headers;
const result = await sendMessage(
"http_post",
JSON.stringify([null, this.reqcopyWith, url, headers, body])
);
return JSON.parse(result);
}
}
''');
}
}
extension ResponseExtexsion on Response {
Map<String, dynamic> toJson() => {
'body': body,
'headers': headers,
'isRedirect': isRedirect,
'persistentConnection': persistentConnection,
'reasonPhrase': reasonPhrase,
'statusCode': statusCode,
'request': {
'contentLength': request?.contentLength,
'finalized': request?.finalized,
'followRedirects': request?.followRedirects,
'headers': request?.headers,
'maxRedirects': request?.maxRedirects,
'method': request?.method,
'persistentConnection': request?.persistentConnection,
'url': request?.url.toString()
}
};
}
extension ToMapExtension on Map? {
Map<String, dynamic>? get toMapStringDynamic {
return this?.map((key, value) => MapEntry(key.toString(), value));
}
Map<String, String>? get toMapStringString {
return this
?.map((key, value) => MapEntry(key.toString(), value.toString()));
}
}

View file

@ -0,0 +1,44 @@
import 'package:flutter_qjs/flutter_qjs.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/browse/extension/providers/extension_preferences_providers.dart';
class JsPreferences {
late JavascriptRuntime runtime;
late Source? source;
JsPreferences(this.runtime, this.source);
init() {
runtime.onMessage('get', (dynamic args) {
return getPreferenceValue(source!.id!, args[0]);
});
runtime.onMessage('getString', (dynamic args) {
return getSourcePreferenceStringValue(source!.id!, args[0], args[1]);
});
runtime.onMessage('setString', (dynamic args) {
return setSourcePreferenceStringValue(source!.id!, args[0], args[1]);
});
runtime.evaluate('''
class SharedPreferences {
get(key) {
return sendMessage(
"get",
JSON.stringify([key])
);
}
getString(key, defaultValue) {
return sendMessage(
"getString",
JSON.stringify([key, defaultValue])
);
}
setString(key, defaultValue) {
return sendMessage(
"setString",
JSON.stringify([key, defaultValue])
);
}
}
''');
}
}

View file

@ -0,0 +1,145 @@
import 'dart:convert';
import 'package:flutter_qjs/flutter_qjs.dart';
import 'package:mangayomi/eval/javascript/dom_selector.dart';
import 'package:mangayomi/eval/javascript/extractors.dart';
import 'package:mangayomi/eval/javascript/http.dart';
import 'package:mangayomi/eval/javascript/preferences.dart';
import 'package:mangayomi/eval/javascript/utils.dart';
import 'package:mangayomi/eval/dart/model/filter.dart';
import 'package:mangayomi/eval/dart/model/m_manga.dart';
import 'package:mangayomi/eval/dart/model/m_pages.dart';
import 'package:mangayomi/eval/dart/model/source_preference.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/models/video.dart';
class JsExtensionService {
late JavascriptRuntime runtime;
late Source? source;
JsExtensionService(this.source);
void _init() {
runtime = getJavascriptRuntime(xhr: false);
JsHttpClient(runtime).init();
JsDomSelector(runtime).init();
JsVideosExtractors(runtime).init();
JsUtils(runtime).init();
JsPreferences(runtime, source).init();
runtime.evaluate('''
class MProvider {
get source() {
return JSON.parse('${jsonEncode(source!.toMSource().toJson())}');
}
async getPopular(page) {
throw new Error("getPopular not implemented");
}
async getLatestUpdates(page) {
throw new Error("getLatestUpdates not implemented");
}
async search(query, page, filters) {
throw new Error("search not implemented");
}
async getDetail(url) {
throw new Error("getDetail not implemented");
}
async getPageList() {
throw new Error("getPageList not implemented");
}
async getVideoList(url) {
throw new Error("getVideoList not implemented");
}
getFilterList() {
throw new Error("getFilterList not implemented");
}
getSourcePreferences() {
throw new Error("getSourcePreferences not implemented");
}
}
async function jsonStringify(fn) {
return JSON.stringify(await fn());
}
''');
runtime.evaluate('''${source!.sourceCode}
var extention = new DefaultExtension();
''');
}
Future<MPages> getPopular(int page) async {
_init();
final res = (await runtime.handlePromise(await runtime
.evaluateAsync('jsonStringify(() => extention.getPopular($page))')))
.stringResult;
return MPages.fromJson(jsonDecode(res));
}
Future<MPages> getLatestUpdates(int page) async {
_init();
final res = (await runtime.handlePromise(await runtime.evaluateAsync(
'jsonStringify(() => extention.getLatestUpdates($page))')))
.stringResult;
return MPages.fromJson(jsonDecode(res));
}
Future<MPages> search(String query, int page, String filters) async {
_init();
final res = (await runtime.handlePromise(await runtime.evaluateAsync(
'jsonStringify(() => extention.search("$query",$page,$filters))')))
.stringResult;
return MPages.fromJson(jsonDecode(res));
}
Future<MManga> getDetail(String url) async {
_init();
final res = (await runtime.handlePromise(await runtime
.evaluateAsync('jsonStringify(() => extention.getDetail("$url"))')))
.stringResult;
return MManga.fromJson(jsonDecode(res));
}
Future<List<String>> getPageList(String url) async {
_init();
final res = (await runtime.handlePromise(await runtime.evaluateAsync(
'jsonStringify(() => extention.getPageList("$url"))')))
.stringResult;
return jsonDecode(res);
}
Future<List<Video>> getVideoList(String url) async {
_init();
final res = (await runtime.handlePromise(await runtime.evaluateAsync(
'jsonStringify(() => extention.getVideoList("$url"))')))
.stringResult;
return (jsonDecode(res) as List).map((e) => Video.fromJson(e)).toList();
}
dynamic getFilterList() {
_init();
try {
final res = runtime
.evaluate('JSON.stringify(extention.getFilterList())')
.stringResult;
return FilterList(fromJsonFilterValuestoList(jsonDecode(res))).filters;
} catch (_) {
return [];
}
}
List<SourcePreference> getSourcePreferences() {
_init();
try {
final res = runtime
.evaluate('JSON.stringify(extention.getSourcePreferences())')
.stringResult;
return (jsonDecode(res) as List)
.map((e) => SourcePreference.fromJson(e)..sourceId = source!.id)
.toList();
} catch (_) {
return [];
}
}
}

View file

@ -0,0 +1,108 @@
import 'package:flutter_qjs/flutter_qjs.dart';
import 'package:js_packer/js_packer.dart';
import 'package:mangayomi/eval/dart/model/m_bridge.dart';
import 'package:mangayomi/utils/cryptoaes/js_unpacker.dart';
import 'package:mangayomi/utils/log/log.dart';
class JsUtils {
late JavascriptRuntime runtime;
JsUtils(this.runtime);
init() {
runtime.onMessage('log', (dynamic args) {
Logger.add(LoggerLevel.warning, "${args[0]}");
return null;
});
runtime.onMessage('cryptoHandler', (dynamic args) {
return MBridge.cryptoHandler(args[0], args[1], args[2], args[3]);
});
runtime.onMessage('encryptAESCryptoJS', (dynamic args) {
return MBridge.encryptAESCryptoJS(args[0], args[1]);
});
runtime.onMessage('decryptAESCryptoJS', (dynamic args) {
return MBridge.decryptAESCryptoJS(args[0], args[1]);
});
runtime.onMessage('deobfuscateJsPassword', (dynamic args) {
return MBridge.deobfuscateJsPassword(args[0]);
});
runtime.onMessage('unpackJsAndCombine', (dynamic args) {
return JsUnpacker.unpackAndCombine(args[0]) ?? "";
});
runtime.onMessage('unpackJs', (dynamic args) {
return JSPacker(args[0]).unpack() ?? "";
});
runtime.evaluate('''
console.log = function (message) {
if (typeof message === "object") {
message = JSON.stringify(message);
}
sendMessage("log", JSON.stringify([message.toString()]));
};
function substringAfter(text, pattern) {
const startIndex = text.indexOf(pattern);
if (startIndex === -1) return text.substring(0);
const start = startIndex + pattern.length;
return text.substring(start);
}
function substringBefore(text, pattern) {
const endIndex = text.indexOf(pattern);
if (endIndex === -1) return text.substring(0);
return text.substring(0, endIndex);
}
function substringBeforeLast(text, pattern) {
const endIndex = text.lastIndexOf(pattern);
if (endIndex === -1) return text.substring(0);
return text.substring(0, endIndex);
}
function substringAfterLast(text, pattern) {
return text.split(pattern).pop();
}
function cryptoHandler(text, iv, secretKeyString, encrypt) {
return sendMessage(
"cryptoHandler",
JSON.stringify([text, iv, secretKeyString, encrypt])
);
}
function encryptAESCryptoJS(plainText, passphrase) {
return sendMessage(
"encryptAESCryptoJS",
JSON.stringify([plainText, passphrase])
);
}
function decryptAESCryptoJS(encrypted, passphrase) {
return sendMessage(
"decryptAESCryptoJS",
JSON.stringify([encrypted, passphrase])
);
}
function deobfuscateJsPassword(inputString) {
return sendMessage(
"deobfuscateJsPassword",
JSON.stringify([inputString])
);
}
function unpackJsAndCombine(scriptBlock) {
return sendMessage(
"unpackJsAndCombine",
JSON.stringify([scriptBlock])
);
}
function unpackJs(packedJS) {
return sendMessage(
"unpackJs",
JSON.stringify([packedJS])
);
}
function parseDates(value, dateFormat, dateFormatLocale) {
return sendMessage(
"parseDates",
JSON.stringify([value, dateFormat, dateFormatLocale])
);
}
''');
}
}

View file

@ -1,93 +0,0 @@
class FilterList {
List<dynamic> filters;
FilterList(this.filters);
}
class SelectFilter {
String? type;
String name;
int state;
List<dynamic> values;
SelectFilter(this.type, this.name, this.state, this.values);
}
class SelectFilterOption {
String name;
String value;
SelectFilterOption(this.name, this.value);
factory SelectFilterOption.fromJson(Map<String, dynamic> json) {
return SelectFilterOption(json['name'], json['value']);
}
}
class SeparatorFilter {
String? type;
SeparatorFilter({this.type = ''});
}
class HeaderFilter {
String? type;
String name;
HeaderFilter(this.name, {this.type = ''});
}
class TextFilter {
String? type;
String name;
String state;
TextFilter(this.type, this.name, {this.state = ""});
}
class SortFilter {
String? type;
String name;
SortState state;
List<dynamic> values;
SortFilter(this.type, this.name, this.state, this.values);
}
class SortState {
int index;
bool ascending;
SortState(this.index, this.ascending);
}
class TriStateFilter {
String? type;
String name;
String value;
int state;
factory TriStateFilter.fromJson(Map<String, dynamic> json) {
return TriStateFilter(json['type'], json['name'], json['value'],
state: json['state'] ?? 0);
}
TriStateFilter(this.type, this.name, this.value, {this.state = 0});
}
class GroupFilter {
String? type;
String name;
List<dynamic> state;
GroupFilter(this.type, this.name, this.state);
}
class CheckBoxFilter {
String? type;
String name;
String value;
bool state;
factory CheckBoxFilter.fromJson(Map<String, dynamic> json) {
return CheckBoxFilter(json['type'], json['name'], json['value'],
state: json['state'] ?? false);
}
CheckBoxFilter(this.type, this.name, this.value, {this.state = false});
}

View file

@ -1,10 +0,0 @@
class MChapter {
String? name;
String? url;
String? dateUpload;
String? scanlator;
MChapter({this.name, this.url, this.dateUpload, this.scanlator});
}

View file

@ -1,33 +0,0 @@
import 'package:mangayomi/eval/model/m_chapter.dart';
import 'package:mangayomi/models/manga.dart';
class MManga {
String? name;
String? link;
String? imageUrl;
String? description;
String? author;
String? artist;
Status? status;
List<String>? genre;
List<MChapter>? chapters;
MManga(
{this.author,
this.artist,
this.genre,
this.imageUrl,
this.link,
this.name,
this.status = Status.unknown,
this.description,
this.chapters});
}

View file

@ -1,7 +0,0 @@
import 'package:mangayomi/eval/model/m_manga.dart';
class MPages {
List<MManga> list;
bool hasNextPage;
MPages({required this.list, this.hasNextPage = false});
}

View file

@ -317,5 +317,7 @@
"text": "النص",
"border": "الحدود",
"background": "الخلفية",
"no_subtite_warning_message": "لا تؤثر لأنه لا توجد مسارات ترجمة في هذا الفيديو"
"no_subtite_warning_message": "لا تؤثر لأنه لا توجد مسارات ترجمة في هذا الفيديو",
"grid_size": "حجم الشبكة",
"n_per_row": "{n} في الصف الواحد"
}

View file

@ -317,5 +317,7 @@
"text": "Text",
"border": "Rand",
"background": "Hintergrund",
"no_subtite_warning_message": "Hat keine Wirkung, da in diesem Video keine Untertitelspuren vorhanden sind"
"no_subtite_warning_message": "Hat keine Wirkung, da in diesem Video keine Untertitelspuren vorhanden sind",
"grid_size": "Rastergröße",
"n_per_row": "{n} pro Zeile"
}

View file

@ -319,5 +319,7 @@
"text": "Text",
"border": "Border",
"background": "Background",
"no_subtite_warning_message": "Has no effect because there aren't any subtitle tracks in this video"
"no_subtite_warning_message": "Has no effect because there aren't any subtitle tracks in this video",
"grid_size": "Grid size",
"n_per_row": "{n} per row"
}

View file

@ -317,5 +317,7 @@
"text": "Texto",
"border": "Borde",
"background": "Fondo",
"no_subtite_warning_message": "No tiene efecto porque no hay pistas de subtítulos en este vídeo"
"no_subtite_warning_message": "No tiene efecto porque no hay pistas de subtítulos en este vídeo",
"grid_size": "Tamaño de la cuadrícula",
"n_per_row": "{n} por fila"
}

View file

@ -317,5 +317,7 @@
"text": "Texto",
"border": "Borde",
"background": "Fondo",
"no_subtite_warning_message": "No tiene efecto porque no hay pistas de subtítulos en este video"
"no_subtite_warning_message": "No tiene efecto porque no hay pistas de subtítulos en este video",
"grid_size": "Tamaño de la cuadrícula",
"n_per_row": "{n} por fila"
}

View file

@ -318,5 +318,7 @@
"text": "Texte",
"border": "Bordure",
"background": "Arrière-plan",
"no_subtite_warning_message": "N'a aucun effet car il n'y a pas de pistes de sous-titres dans cette vidéo"
"no_subtite_warning_message": "N'a aucun effet car il n'y a pas de pistes de sous-titres dans cette vidéo",
"grid_size": "Taille de la grille",
"n_per_row": "{n} par ligne"
}

View file

@ -317,5 +317,7 @@
"text": "Teks",
"border": "Batas",
"background": "Latar Belakang",
"no_subtite_warning_message": "Tidak berpengaruh karena tidak ada trek subtitle dalam video ini"
"no_subtite_warning_message": "Tidak berpengaruh karena tidak ada trek subtitle dalam video ini",
"grid_size": "Ukuran Grid",
"n_per_row": "{n} per baris"
}

View file

@ -317,5 +317,7 @@
"text": "Testo",
"border": "Bordo",
"background": "Sfondo",
"no_subtite_warning_message": "Non ha effetto perché non ci sono tracce sottotitoli in questo video"
"no_subtite_warning_message": "Non ha effetto perché non ci sono tracce sottotitoli in questo video",
"grid_size": "Dimensione griglia",
"n_per_row": "{n} per riga"
}

View file

@ -317,5 +317,7 @@
"text": "Texto",
"border": "Borda",
"background": "Fundo",
"no_subtite_warning_message": "Não tem efeito porque não há faixas de legendas neste vídeo"
"no_subtite_warning_message": "Não tem efeito porque não há faixas de legendas neste vídeo",
"grid_size": "Tamanho da grade",
"n_per_row": "{n} por linha"
}

View file

@ -317,5 +317,7 @@
"text": "Texto",
"border": "Borda",
"background": "Fundo",
"no_subtite_warning_message": "Não tem efeito porque não há faixas de legenda neste vídeo"
"no_subtite_warning_message": "Não tem efeito porque não há faixas de legenda neste vídeo",
"grid_size": "Tamanho da grade",
"n_per_row": "{n} por linha"
}

View file

@ -317,5 +317,7 @@
"text": "Текст",
"border": "Граница",
"background": "Фон",
"no_subtite_warning_message": "Не имеет эффекта, потому что в этом видео нет субтитров"
"no_subtite_warning_message": "Не имеет эффекта, потому что в этом видео нет субтитров",
"grid_size": "Размер сетки",
"n_per_row": "{n} в ряд"
}

View file

@ -317,5 +317,7 @@
"text": "Metin",
"border": "Kenarlık",
"background": "Arka Plan",
"no_subtite_warning_message": "Bu videoda altyazı parçaları olmadığı için etkisi yok"
"no_subtite_warning_message": "Bu videoda altyazı parçaları olmadığı için etkisi yok",
"grid_size": "Kılavuz Boyutu",
"n_per_row": "{n} satır başına"
}

View file

@ -319,5 +319,7 @@
"text": "文本",
"border": "边框",
"background": "背景",
"no_subtite_warning_message": "由于此视频中没有字幕轨道,因此无效。"
"no_subtite_warning_message": "由于此视频中没有字幕轨道,因此无效。",
"grid_size": "网格大小",
"n_per_row": "{n} 每行"
}

View file

@ -5,6 +5,7 @@ import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_api_availability/google_api_availability.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart';
@ -25,6 +26,8 @@ import 'package:window_manager/window_manager.dart';
// Global instance of the Isar database.
late Isar isar;
bool hasGPServices = false;
/// Overrides the default HTTP client to allow all certificates
class MyHttpoverrides extends HttpOverrides {
@override
@ -57,6 +60,13 @@ void main(List<String> args) async {
isar = await StorageProvider().initDB(null, inspector: kDebugMode);
await StorageProvider().requestPermission();
GoogleFonts.aBeeZee();
if (Platform.isAndroid) {
try {
hasGPServices = (await GoogleApiAvailability.instance
.checkGooglePlayServicesAvailability()) ==
GooglePlayServicesAvailability.success;
} catch (_) {}
}
// Start the app.
runApp(const ProviderScope(child: MyApp()));
}

View file

@ -179,6 +179,10 @@ class Settings {
String? appFontFamily;
int? mangaGridSize;
int? animeGridSize;
Settings(
{this.id = 227,
this.displayType = DisplayType.compactGrid,
@ -257,7 +261,9 @@ class Settings {
this.colorFilterBlendMode = ColorFilterBlendMode.none,
this.playerSubtitleSettings,
this.mangaHomeDisplayType = DisplayType.comfortableGrid,
this.appFontFamily});
this.appFontFamily,
this.mangaGridSize,
this.animeGridSize});
Settings.fromJson(Map<String, dynamic> json) {
animatePageTransitions = json['animatePageTransitions'];
@ -398,6 +404,8 @@ class Settings {
: null;
mangaHomeDisplayType = DisplayType.values[json['mangaHomeDisplayType']];
appFontFamily = json['appFontFamily'];
mangaGridSize = json['mangaGridSize'];
animeGridSize = json['animeGridSize'];
}
Map<String, dynamic> toJson() => {
@ -502,7 +510,9 @@ class Settings {
if (playerSubtitleSettings != null)
'playerSubtitleSettings': playerSubtitleSettings!.toJson(),
'mangaHomeDisplayType': mangaHomeDisplayType.index,
'appFontFamily': appFontFamily
'appFontFamily': appFontFamily,
'mangaGridSize': mangaGridSize,
'animeGridSize': animeGridSize
};
}

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/m_source.dart';
import 'package:mangayomi/eval/dart/model/m_source.dart';
part 'source.g.dart';
@collection
@ -53,6 +53,13 @@ class Source {
String? additionalParams;
bool? isLocal;
bool? isObsolete;
@enumerated
SourceCodeLanguage sourceCodeLanguage = SourceCodeLanguage.dart;
Source(
{this.id = 0,
this.name = '',
@ -77,7 +84,9 @@ class Source {
this.headers = '',
this.isManga = true,
this.appMinVerReq = "",
this.additionalParams = ""});
this.additionalParams = "",
this.isLocal = false,
this.isObsolete = false});
Source.fromJson(Map<String, dynamic> json) {
apiUrl = json['apiUrl'];
@ -104,6 +113,10 @@ class Source {
version = json['version'];
versionLast = json['versionLast'];
additionalParams = json['additionalParams'] ?? "";
isObsolete = json['isObsolete'];
isLocal = json['isLocal'];
sourceCodeLanguage =
SourceCodeLanguage.values[json['sourceCodeLanguage'] ?? 0];
}
Map<String, dynamic> toJson() => {
@ -131,6 +144,9 @@ class Source {
'version': version,
'versionLast': versionLast,
'additionalParams': additionalParams,
'sourceCodeLanguage': sourceCodeLanguage.index,
'isObsolete': isObsolete,
'isLocal': isLocal
};
bool get isTorrent => (typeSource?.toLowerCase() ?? "") == "torrent";
@ -149,3 +165,5 @@ class Source {
additionalParams: additionalParams);
}
}
enum SourceCodeLanguage { dart, javascript }

View file

@ -77,63 +77,79 @@ const SourceSchema = CollectionSchema(
name: r'isFullData',
type: IsarType.bool,
),
r'isManga': PropertySchema(
r'isLocal': PropertySchema(
id: 12,
name: r'isLocal',
type: IsarType.bool,
),
r'isManga': PropertySchema(
id: 13,
name: r'isManga',
type: IsarType.bool,
),
r'isNsfw': PropertySchema(
id: 13,
id: 14,
name: r'isNsfw',
type: IsarType.bool,
),
r'isObsolete': PropertySchema(
id: 15,
name: r'isObsolete',
type: IsarType.bool,
),
r'isPinned': PropertySchema(
id: 14,
id: 16,
name: r'isPinned',
type: IsarType.bool,
),
r'isTorrent': PropertySchema(
id: 15,
id: 17,
name: r'isTorrent',
type: IsarType.bool,
),
r'lang': PropertySchema(
id: 16,
id: 18,
name: r'lang',
type: IsarType.string,
),
r'lastUsed': PropertySchema(
id: 17,
id: 19,
name: r'lastUsed',
type: IsarType.bool,
),
r'name': PropertySchema(
id: 18,
id: 20,
name: r'name',
type: IsarType.string,
),
r'sourceCode': PropertySchema(
id: 19,
id: 21,
name: r'sourceCode',
type: IsarType.string,
),
r'sourceCodeLanguage': PropertySchema(
id: 22,
name: r'sourceCodeLanguage',
type: IsarType.byte,
enumMap: _SourcesourceCodeLanguageEnumValueMap,
),
r'sourceCodeUrl': PropertySchema(
id: 20,
id: 23,
name: r'sourceCodeUrl',
type: IsarType.string,
),
r'typeSource': PropertySchema(
id: 21,
id: 24,
name: r'typeSource',
type: IsarType.string,
),
r'version': PropertySchema(
id: 22,
id: 25,
name: r'version',
type: IsarType.string,
),
r'versionLast': PropertySchema(
id: 23,
id: 26,
name: r'versionLast',
type: IsarType.string,
)
@ -269,18 +285,21 @@ void _sourceSerialize(
writer.writeBool(offsets[9], object.isActive);
writer.writeBool(offsets[10], object.isAdded);
writer.writeBool(offsets[11], object.isFullData);
writer.writeBool(offsets[12], object.isManga);
writer.writeBool(offsets[13], object.isNsfw);
writer.writeBool(offsets[14], object.isPinned);
writer.writeBool(offsets[15], object.isTorrent);
writer.writeString(offsets[16], object.lang);
writer.writeBool(offsets[17], object.lastUsed);
writer.writeString(offsets[18], object.name);
writer.writeString(offsets[19], object.sourceCode);
writer.writeString(offsets[20], object.sourceCodeUrl);
writer.writeString(offsets[21], object.typeSource);
writer.writeString(offsets[22], object.version);
writer.writeString(offsets[23], object.versionLast);
writer.writeBool(offsets[12], object.isLocal);
writer.writeBool(offsets[13], object.isManga);
writer.writeBool(offsets[14], object.isNsfw);
writer.writeBool(offsets[15], object.isObsolete);
writer.writeBool(offsets[16], object.isPinned);
writer.writeBool(offsets[17], object.isTorrent);
writer.writeString(offsets[18], object.lang);
writer.writeBool(offsets[19], object.lastUsed);
writer.writeString(offsets[20], object.name);
writer.writeString(offsets[21], object.sourceCode);
writer.writeByte(offsets[22], object.sourceCodeLanguage.index);
writer.writeString(offsets[23], object.sourceCodeUrl);
writer.writeString(offsets[24], object.typeSource);
writer.writeString(offsets[25], object.version);
writer.writeString(offsets[26], object.versionLast);
}
Source _sourceDeserialize(
@ -303,18 +322,23 @@ Source _sourceDeserialize(
isActive: reader.readBoolOrNull(offsets[9]),
isAdded: reader.readBoolOrNull(offsets[10]),
isFullData: reader.readBoolOrNull(offsets[11]),
isManga: reader.readBoolOrNull(offsets[12]),
isNsfw: reader.readBoolOrNull(offsets[13]),
isPinned: reader.readBoolOrNull(offsets[14]),
lang: reader.readStringOrNull(offsets[16]),
lastUsed: reader.readBoolOrNull(offsets[17]),
name: reader.readStringOrNull(offsets[18]),
sourceCode: reader.readStringOrNull(offsets[19]),
sourceCodeUrl: reader.readStringOrNull(offsets[20]),
typeSource: reader.readStringOrNull(offsets[21]),
version: reader.readStringOrNull(offsets[22]),
versionLast: reader.readStringOrNull(offsets[23]),
isLocal: reader.readBoolOrNull(offsets[12]),
isManga: reader.readBoolOrNull(offsets[13]),
isNsfw: reader.readBoolOrNull(offsets[14]),
isObsolete: reader.readBoolOrNull(offsets[15]),
isPinned: reader.readBoolOrNull(offsets[16]),
lang: reader.readStringOrNull(offsets[18]),
lastUsed: reader.readBoolOrNull(offsets[19]),
name: reader.readStringOrNull(offsets[20]),
sourceCode: reader.readStringOrNull(offsets[21]),
sourceCodeUrl: reader.readStringOrNull(offsets[23]),
typeSource: reader.readStringOrNull(offsets[24]),
version: reader.readStringOrNull(offsets[25]),
versionLast: reader.readStringOrNull(offsets[26]),
);
object.sourceCodeLanguage = _SourcesourceCodeLanguageValueEnumMap[
reader.readByteOrNull(offsets[22])] ??
SourceCodeLanguage.dart;
return object;
}
@ -356,28 +380,45 @@ P _sourceDeserializeProp<P>(
case 14:
return (reader.readBoolOrNull(offset)) as P;
case 15:
return (reader.readBool(offset)) as P;
case 16:
return (reader.readStringOrNull(offset)) as P;
case 17:
return (reader.readBoolOrNull(offset)) as P;
case 16:
return (reader.readBoolOrNull(offset)) as P;
case 17:
return (reader.readBool(offset)) as P;
case 18:
return (reader.readStringOrNull(offset)) as P;
case 19:
return (reader.readStringOrNull(offset)) as P;
return (reader.readBoolOrNull(offset)) as P;
case 20:
return (reader.readStringOrNull(offset)) as P;
case 21:
return (reader.readStringOrNull(offset)) as P;
case 22:
return (reader.readStringOrNull(offset)) as P;
return (_SourcesourceCodeLanguageValueEnumMap[
reader.readByteOrNull(offset)] ??
SourceCodeLanguage.dart) as P;
case 23:
return (reader.readStringOrNull(offset)) as P;
case 24:
return (reader.readStringOrNull(offset)) as P;
case 25:
return (reader.readStringOrNull(offset)) as P;
case 26:
return (reader.readStringOrNull(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
}
const _SourcesourceCodeLanguageEnumValueMap = {
'dart': 0,
'javascript': 1,
};
const _SourcesourceCodeLanguageValueEnumMap = {
0: SourceCodeLanguage.dart,
1: SourceCodeLanguage.javascript,
};
Id _sourceGetId(Source object) {
return object.id ?? Isar.autoIncrement;
}
@ -1816,6 +1857,32 @@ extension SourceQueryFilter on QueryBuilder<Source, Source, QFilterCondition> {
});
}
QueryBuilder<Source, Source, QAfterFilterCondition> isLocalIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'isLocal',
));
});
}
QueryBuilder<Source, Source, QAfterFilterCondition> isLocalIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNotNull(
property: r'isLocal',
));
});
}
QueryBuilder<Source, Source, QAfterFilterCondition> isLocalEqualTo(
bool? value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'isLocal',
value: value,
));
});
}
QueryBuilder<Source, Source, QAfterFilterCondition> isMangaIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
@ -1868,6 +1935,32 @@ extension SourceQueryFilter on QueryBuilder<Source, Source, QFilterCondition> {
});
}
QueryBuilder<Source, Source, QAfterFilterCondition> isObsoleteIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'isObsolete',
));
});
}
QueryBuilder<Source, Source, QAfterFilterCondition> isObsoleteIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNotNull(
property: r'isObsolete',
));
});
}
QueryBuilder<Source, Source, QAfterFilterCondition> isObsoleteEqualTo(
bool? value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'isObsolete',
value: value,
));
});
}
QueryBuilder<Source, Source, QAfterFilterCondition> isPinnedIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
@ -2366,6 +2459,61 @@ extension SourceQueryFilter on QueryBuilder<Source, Source, QFilterCondition> {
});
}
QueryBuilder<Source, Source, QAfterFilterCondition> sourceCodeLanguageEqualTo(
SourceCodeLanguage value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'sourceCodeLanguage',
value: value,
));
});
}
QueryBuilder<Source, Source, QAfterFilterCondition>
sourceCodeLanguageGreaterThan(
SourceCodeLanguage value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'sourceCodeLanguage',
value: value,
));
});
}
QueryBuilder<Source, Source, QAfterFilterCondition>
sourceCodeLanguageLessThan(
SourceCodeLanguage value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'sourceCodeLanguage',
value: value,
));
});
}
QueryBuilder<Source, Source, QAfterFilterCondition> sourceCodeLanguageBetween(
SourceCodeLanguage lower,
SourceCodeLanguage upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'sourceCodeLanguage',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
));
});
}
QueryBuilder<Source, Source, QAfterFilterCondition> sourceCodeUrlIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
@ -3101,6 +3249,18 @@ extension SourceQuerySortBy on QueryBuilder<Source, Source, QSortBy> {
});
}
QueryBuilder<Source, Source, QAfterSortBy> sortByIsLocal() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isLocal', Sort.asc);
});
}
QueryBuilder<Source, Source, QAfterSortBy> sortByIsLocalDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isLocal', Sort.desc);
});
}
QueryBuilder<Source, Source, QAfterSortBy> sortByIsManga() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isManga', Sort.asc);
@ -3125,6 +3285,18 @@ extension SourceQuerySortBy on QueryBuilder<Source, Source, QSortBy> {
});
}
QueryBuilder<Source, Source, QAfterSortBy> sortByIsObsolete() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isObsolete', Sort.asc);
});
}
QueryBuilder<Source, Source, QAfterSortBy> sortByIsObsoleteDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isObsolete', Sort.desc);
});
}
QueryBuilder<Source, Source, QAfterSortBy> sortByIsPinned() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isPinned', Sort.asc);
@ -3197,6 +3369,18 @@ extension SourceQuerySortBy on QueryBuilder<Source, Source, QSortBy> {
});
}
QueryBuilder<Source, Source, QAfterSortBy> sortBySourceCodeLanguage() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'sourceCodeLanguage', Sort.asc);
});
}
QueryBuilder<Source, Source, QAfterSortBy> sortBySourceCodeLanguageDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'sourceCodeLanguage', Sort.desc);
});
}
QueryBuilder<Source, Source, QAfterSortBy> sortBySourceCodeUrl() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'sourceCodeUrl', Sort.asc);
@ -3403,6 +3587,18 @@ extension SourceQuerySortThenBy on QueryBuilder<Source, Source, QSortThenBy> {
});
}
QueryBuilder<Source, Source, QAfterSortBy> thenByIsLocal() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isLocal', Sort.asc);
});
}
QueryBuilder<Source, Source, QAfterSortBy> thenByIsLocalDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isLocal', Sort.desc);
});
}
QueryBuilder<Source, Source, QAfterSortBy> thenByIsManga() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isManga', Sort.asc);
@ -3427,6 +3623,18 @@ extension SourceQuerySortThenBy on QueryBuilder<Source, Source, QSortThenBy> {
});
}
QueryBuilder<Source, Source, QAfterSortBy> thenByIsObsolete() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isObsolete', Sort.asc);
});
}
QueryBuilder<Source, Source, QAfterSortBy> thenByIsObsoleteDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isObsolete', Sort.desc);
});
}
QueryBuilder<Source, Source, QAfterSortBy> thenByIsPinned() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isPinned', Sort.asc);
@ -3499,6 +3707,18 @@ extension SourceQuerySortThenBy on QueryBuilder<Source, Source, QSortThenBy> {
});
}
QueryBuilder<Source, Source, QAfterSortBy> thenBySourceCodeLanguage() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'sourceCodeLanguage', Sort.asc);
});
}
QueryBuilder<Source, Source, QAfterSortBy> thenBySourceCodeLanguageDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'sourceCodeLanguage', Sort.desc);
});
}
QueryBuilder<Source, Source, QAfterSortBy> thenBySourceCodeUrl() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'sourceCodeUrl', Sort.asc);
@ -3631,6 +3851,12 @@ extension SourceQueryWhereDistinct on QueryBuilder<Source, Source, QDistinct> {
});
}
QueryBuilder<Source, Source, QDistinct> distinctByIsLocal() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'isLocal');
});
}
QueryBuilder<Source, Source, QDistinct> distinctByIsManga() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'isManga');
@ -3643,6 +3869,12 @@ extension SourceQueryWhereDistinct on QueryBuilder<Source, Source, QDistinct> {
});
}
QueryBuilder<Source, Source, QDistinct> distinctByIsObsolete() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'isObsolete');
});
}
QueryBuilder<Source, Source, QDistinct> distinctByIsPinned() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'isPinned');
@ -3682,6 +3914,12 @@ extension SourceQueryWhereDistinct on QueryBuilder<Source, Source, QDistinct> {
});
}
QueryBuilder<Source, Source, QDistinct> distinctBySourceCodeLanguage() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'sourceCodeLanguage');
});
}
QueryBuilder<Source, Source, QDistinct> distinctBySourceCodeUrl(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
@ -3791,6 +4029,12 @@ extension SourceQueryProperty on QueryBuilder<Source, Source, QQueryProperty> {
});
}
QueryBuilder<Source, bool?, QQueryOperations> isLocalProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'isLocal');
});
}
QueryBuilder<Source, bool?, QQueryOperations> isMangaProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'isManga');
@ -3803,6 +4047,12 @@ extension SourceQueryProperty on QueryBuilder<Source, Source, QQueryProperty> {
});
}
QueryBuilder<Source, bool?, QQueryOperations> isObsoleteProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'isObsolete');
});
}
QueryBuilder<Source, bool?, QQueryOperations> isPinnedProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'isPinned');
@ -3839,6 +4089,13 @@ extension SourceQueryProperty on QueryBuilder<Source, Source, QQueryProperty> {
});
}
QueryBuilder<Source, SourceCodeLanguage, QQueryOperations>
sourceCodeLanguageProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'sourceCodeLanguage');
});
}
QueryBuilder<Source, String?, QQueryOperations> sourceCodeUrlProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'sourceCodeUrl');

View file

@ -1,3 +1,5 @@
import 'package:mangayomi/eval/javascript/http.dart';
class Video {
String url;
String quality;
@ -8,6 +10,24 @@ class Video {
Video(this.url, this.quality, this.originalUrl,
{this.headers, this.subtitles, this.audios});
factory Video.fromJson(Map<String, dynamic> json) {
return Video(json['url'], json['quality'], json['originalUrl'],
headers: (json['headers'] as Map?).toMapStringString,
subtitles: json['subtitles'] != null
? (json['subtitles'] as List).map((e) => Track.fromJson(e)).toList()
: [],
audios: json['audios'] != null
? (json['audios'] as List).map((e) => Track.fromJson(e)).toList()
: []);
}
Map<String, dynamic> toJson() => {
'url': url,
'quality': quality,
'originalUrl': originalUrl,
'headers': headers,
'subtitles': subtitles?.map((e) => e.toJson()).toList(),
'audios': audios?.map((e) => e.toJson()).toList(),
};
}
class Track {
@ -19,4 +39,5 @@ class Track {
file = json['file'];
label = json['label'];
}
Map<String, dynamic> toJson() => {'file': file, 'label': label};
}

View file

@ -7,7 +7,7 @@ part of 'state_provider.dart';
// **************************************************************************
String _$subtitleSettingsStateHash() =>
r'4f668c79675772a76d80585db43d041675d0d178';
r'4b89ea55392e662651d5aeee4dfce2fcd23ac0e7';
/// See also [SubtitleSettingsState].
@ProviderFor(SubtitleSettingsState)

View file

@ -4,7 +4,7 @@ import 'package:go_router/go_router.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/browse/extension/providers/fetch_manga_sources.dart';
import 'package:mangayomi/services/fetch_manga_sources.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/providers/storage_provider.dart';
@ -74,29 +74,41 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
},
controller: _textEditingController,
)
: _tabBarController.index != 4
? IconButton(
splashRadius: 20,
onPressed: () {
if (_tabBarController.index != 1 &&
_tabBarController.index != 0) {
setState(() {
_isSearch = true;
});
} else {
context.push('/globalSearch',
extra: _tabBarController.index == 0
? true
: false);
}
},
icon: Icon(
_tabBarController.index == 0 ||
_tabBarController.index == 1
? Icons.travel_explore_rounded
: Icons.search_rounded,
color: Theme.of(context).hintColor))
: Container(),
: Row(
children: [
if (_tabBarController.index == 2 ||
_tabBarController.index == 3)
IconButton(
onPressed: () {
context.push('/createExtension');
},
icon: Icon(Icons.add_outlined,
color: Theme.of(context).hintColor)),
_tabBarController.index != 4
? IconButton(
splashRadius: 20,
onPressed: () {
if (_tabBarController.index != 1 &&
_tabBarController.index != 0) {
setState(() {
_isSearch = true;
});
} else {
context.push('/globalSearch',
extra: _tabBarController.index == 0
? true
: false);
}
},
icon: Icon(
_tabBarController.index == 0 ||
_tabBarController.index == 1
? Icons.travel_explore_rounded
: Icons.search_rounded,
color: Theme.of(context).hintColor))
: Container(),
],
),
IconButton(
splashRadius: 20,
onPressed: () {

View file

@ -0,0 +1,461 @@
import 'package:flutter/material.dart';
import 'package:flutter_code_editor/flutter_code_editor.dart';
import 'package:flutter_highlight/themes/atom-one-dark.dart';
import 'package:json_view/json_view.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:highlight/languages/dart.dart';
import 'package:highlight/languages/javascript.dart';
import 'package:mangayomi/eval/dart/bridge/m_source.dart';
import 'package:mangayomi/eval/dart/compiler/compiler.dart';
import 'package:mangayomi/eval/dart/model/m_provider.dart';
import 'package:mangayomi/eval/dart/runtime/runtime.dart';
import 'package:mangayomi/eval/javascript/service.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/manga/home/widget/filter_widget.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/services/get_detail.dart';
import 'package:mangayomi/services/get_filter_list.dart';
import 'package:mangayomi/services/get_latest_updates.dart';
import 'package:mangayomi/services/get_popular.dart';
import 'package:mangayomi/services/search.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/utils/log/log.dart';
class CodeEditor extends ConsumerStatefulWidget {
final int? sourceId;
const CodeEditor({super.key, this.sourceId});
@override
ConsumerState<CodeEditor> createState() => _CodeEditorState();
}
class _CodeEditorState extends ConsumerState<CodeEditor> {
dynamic result;
late final source =
widget.sourceId == null ? null : isar.sources.getSync(widget.sourceId!);
late final controller = CodeController(
text: source?.sourceCode ?? "",
language: source == null
? dart
: source!.sourceCodeLanguage == SourceCodeLanguage.dart
? dart
: javascript,
);
List<(String, int)> _getServices(BuildContext context) => [
("getPopular", 0),
("getLatestUpdates", 1),
("search", 2),
("getDetail", 3),
("getPageList", 4),
("getVideoList", 5)
];
int _serviceIndex = 0;
int _page = 1;
String _query = "";
String _url = "";
bool _isLoading = false;
String _errorText = "";
bool _error = false;
final _logsNotifier =
ValueNotifier<List<(LoggerLevel, String, DateTime)>>([]);
late final _logStreamController = Logger.logStreamController;
final _scrollController = ScrollController();
@override
void initState() {
_logStreamController.stream.asBroadcastStream().listen((event) async {
_logsNotifier.value.add(event);
try {
await Future.delayed(const Duration(milliseconds: 5));
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
} catch (_) {}
});
super.initState();
}
List<dynamic> filters = [];
Future<String?> filterDialog(BuildContext context) async {
return await showModalBottomSheet(
context: context,
builder: (context) => StatefulBuilder(builder: (context, setState) {
return Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
TextButton(
onPressed: () {
setState(() {
filters = getFilterList(source: source!);
});
},
child: Text(context.l10n.reset),
),
const Spacer(),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: context.primaryColor),
onPressed: () {
Navigator.pop(context, 'filter');
},
child: Text(
context.l10n.filter,
style: TextStyle(
color: Theme.of(context).scaffoldBackgroundColor),
),
),
],
),
),
const Divider(),
Expanded(
child: FilterWidget(
filterList: filters,
onChanged: (values) {
setState(() {
filters = values;
});
},
),
),
],
);
}),
);
}
@override
void dispose() {
super.dispose();
_scrollController.dispose();
}
@override
Widget build(BuildContext context) {
final filterList = source != null ? getFilterList(source: source!) : [];
return Scaffold(
appBar: AppBar(
leading: BackButton(onPressed: () => Navigator.pop(context, source)),
),
body: Column(
children: [
Expanded(
child: Row(
children: [
Flexible(
flex: 7,
child: CodeTheme(
data: CodeThemeData(styles: atomOneDarkTheme),
child: SingleChildScrollView(
child: CodeField(
controller: controller,
onChanged: (a) {
setState(() {
source?.sourceCode = a;
});
if (source != null) {
isar.writeTxnSync(
() => isar.sources.putSync(source!));
}
},
),
),
)),
Flexible(
flex: 3,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: DropdownButton(
icon: const Icon(Icons.keyboard_arrow_down),
isExpanded: true,
value: _serviceIndex,
hint: Text(_getServices(context)[_serviceIndex].$1,
style: const TextStyle(fontSize: 13)),
items: _getServices(context)
.map((e) => DropdownMenuItem(
value: e.$2,
child: Text(e.$1,
style: const TextStyle(fontSize: 13)),
))
.toList(),
onChanged: (v) {
setState(() {
_serviceIndex = v!;
});
},
),
),
if (_serviceIndex == 0 ||
_serviceIndex == 1 ||
_serviceIndex == 2)
_textEditing("Page", context, "ex: 1", (v) {
_page = int.tryParse(v) ?? 1;
}),
if (_serviceIndex == 2)
_textEditing("Query", context, "ex: one piece", (v) {
_query = v;
}),
if (_serviceIndex == 3 ||
_serviceIndex == 4 ||
_serviceIndex == 5)
_textEditing("Url", context, "ex: url of the entry",
(v) {
_url = v;
}),
Padding(
padding: const EdgeInsets.all(8.0),
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
setState(() {
result = null;
_isLoading = true;
_error = false;
_errorText = "";
});
if (source != null) {
try {
if (_serviceIndex == 0) {
final getManga = await ref.watch(
getPopularProvider(
source: source!,
page: _page)
.future);
result = getManga!.toJson();
} else if (_serviceIndex == 1) {
final getManga = await ref.watch(
getLatestUpdatesProvider(
source: source!,
page: _page)
.future);
result = getManga!.toJson();
} else if (_serviceIndex == 2) {
final getManga = await ref.watch(
searchProvider(
source: source!,
query: _query,
page: _page,
filterList: filterList)
.future);
result = getManga!.toJson();
} else if (_serviceIndex == 3) {
final getManga = await ref.watch(
getDetailProvider(
source: source!, url: _url)
.future);
result = getManga.toJson();
} else if (_serviceIndex == 4) {
if (source!.sourceCodeLanguage ==
SourceCodeLanguage.dart) {
final bytecode =
compilerEval(source!.sourceCode!);
final runtime = runtimeEval(bytecode);
var res = await runtime.executeLib(
'package:mangayomi/main.dart',
'main', [
$MSource.wrap(source!.toMSource())
]);
result = await (res as MProvider)
.getPageList(_url);
} else {
result =
await JsExtensionService(source)
.getPageList(_url);
}
result = {"pages": result};
} else {
if (source!.sourceCodeLanguage ==
SourceCodeLanguage.dart) {
final bytecode =
compilerEval(source!.sourceCode!);
final runtime = runtimeEval(bytecode);
var res = runtime.executeLib(
'package:mangayomi/main.dart',
'main', [
$MSource.wrap(source!.toMSource())
]);
result = (await (res as MProvider)
.getVideoList(_url))
.map((e) => e.toJson())
.toList();
} else {
result =
(await JsExtensionService(source)
.getVideoList(_url))
.map((e) => e.toJson())
.toList();
}
}
if (mounted) {
setState(() {
_isLoading = false;
});
}
} catch (e) {
if (mounted) {
setState(() {
_error = true;
_errorText = e.toString();
_isLoading = false;
});
}
}
}
},
child: const Text("Execute")),
Padding(
padding: const EdgeInsets.all(10),
child: ElevatedButton(
onPressed: () {
setState(() {
result = null;
_isLoading = false;
_error = false;
_errorText = "";
filters = [];
});
},
child: Text(context.l10n.reset)),
),
if (_serviceIndex == 2 && filterList.isNotEmpty)
ElevatedButton(
onPressed: () async {
if (source != null) {
try {
if (filters.isEmpty) {
filters = filterList;
}
final res = await filterDialog(context);
if (res == 'filter' && mounted) {
setState(() {
result = null;
_isLoading = true;
_error = false;
_errorText = "";
});
final getManga = await ref.watch(
searchProvider(
source: source!,
query: _query,
page: _page,
filterList: filters)
.future);
result = getManga!.toJson();
setState(() {
_isLoading = false;
});
}
} catch (e) {
setState(() {
_error = true;
_errorText = e.toString();
_isLoading = false;
});
}
}
},
child: Text(context.l10n.filter)),
],
),
),
Expanded(
child: _error
? SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_errorText),
],
),
)
: _isLoading
? const Center(
child: CircularProgressIndicator())
: result != null
? JsonConfig(
data: JsonConfigData(
gap: 100,
style: const JsonStyleScheme(
quotation:
JsonQuotation.same('"'),
openAtStart: false,
arrow: Icon(Icons.arrow_forward),
depth: 4,
),
color: const JsonColorScheme(),
),
child: JsonView(json: result),
)
: const SizedBox.shrink())
],
),
),
],
),
),
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 0.5),
borderRadius: BorderRadius.circular(5),
color: Colors.black,
),
width: context.mediaWidth(1),
height: 200,
child: ValueListenableBuilder(
valueListenable: _logsNotifier,
builder: (context, logs, child) => ListView.separated(
separatorBuilder: (context, index) => const Divider(),
controller: _scrollController,
padding: const EdgeInsets.all(10),
itemCount: logs.length,
itemBuilder: (context, index) {
final value = logs[index];
return SelectableText(value.$2,
style: TextStyle(
color: value.$1 == LoggerLevel.warning
? Colors.yellow
: Colors.blueAccent));
},
),
),
)
],
),
);
}
}
Widget _textEditing(String label, BuildContext context, String hintText,
void Function(String)? onChanged) {
return Padding(
padding: const EdgeInsets.all(4),
child: TextFormField(
keyboardType: TextInputType.number,
onChanged: onChanged,
decoration: InputDecoration(
hintText: hintText,
labelText: label,
isDense: true,
filled: true,
fillColor: Colors.transparent,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: context.dynamicThemeColor)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: context.dynamicThemeColor)),
border: OutlineInputBorder(
borderSide: BorderSide(color: context.dynamicThemeColor))),
),
);
}

View file

@ -1,13 +1,16 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/eval/model/source_preference.dart';
import 'package:go_router/go_router.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/dart/model/source_preference.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/browse/extension/providers/extension_preferences_providers.dart';
import 'package:mangayomi/modules/browse/extension/widgets/source_preference_widget.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/services/get_source_preference.dart';
import 'package:mangayomi/sources/source_test.dart';
import 'package:mangayomi/utils/cached_network.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/utils/language.dart';
@ -31,8 +34,8 @@ class _ExtensionDetailState extends ConsumerState<ExtensionDetail> {
final l10n = l10nLocalizations(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.extension_detail),
),
title: Text(l10n.extension_detail),
leading: BackButton(onPressed: () => Navigator.pop(context, source))),
body: SingleChildScrollView(
child: Column(
children: [
@ -45,20 +48,19 @@ class _ExtensionDetailState extends ConsumerState<ExtensionDetail> {
borderRadius: BorderRadius.circular(10)),
child: widget.source.iconUrl!.isEmpty
? const Icon(Icons.source_outlined, size: 140)
: CachedNetworkImage(
: cachedNetworkImage(
imageUrl: widget.source.iconUrl!,
fit: BoxFit.contain,
width: 140,
height: 140,
errorWidget: (context, url, error) {
return const SizedBox(
width: 140,
height: 140,
child: Center(
child: Icon(Icons.source_outlined, size: 140),
),
);
},
errorWidget: const SizedBox(
width: 140,
height: 140,
child: Center(
child: Icon(Icons.source_outlined, size: 140),
),
),
headers: {},
),
),
),
@ -127,6 +129,47 @@ class _ExtensionDetailState extends ConsumerState<ExtensionDetail> {
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: context.mediaWidth(1),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(0),
backgroundColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)),
elevation: 0,
shadowColor: Colors.transparent),
onPressed: () async {
final res =
await context.push('/codeEditor', extra: source.id);
if (res != null && mounted) {
setState(() {
source = res as Source;
sourcePreference = getSourcePreference(source: source)
.map((e) =>
getSourcePreferenceEntry(e.key!, source.id!))
.toList();
});
}
},
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Text(
"Edit code",
style: TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
),
),
Icon(Icons.code)
],
)),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
@ -165,11 +208,34 @@ class _ExtensionDetailState extends ConsumerState<ExtensionDetail> {
),
TextButton(
onPressed: () {
isar.writeTxnSync(() =>
final sourcePrefsIds = isar
.sourcePreferences
.filter()
.sourceIdEqualTo(source.id!)
.findAllSync()
.map((e) => e.id!)
.toList();
final sourcePrefsStringIds = isar
.sourcePreferenceStringValues
.filter()
.sourceIdEqualTo(source.id!)
.findAllSync()
.map((e) => e.id)
.toList();
isar.writeTxnSync(() {
if (!useTestSourceCode) {
isar.sources.putSync(widget.source
..sourceCode = ""
..isAdded = false
..isPinned = false));
..isPinned = false);
}
isar.sourcePreferences
.deleteAllSync(sourcePrefsIds);
isar.sourcePreferenceStringValues
.deleteAllSync(
sourcePrefsStringIds);
});
Navigator.pop(ctx);
Navigator.pop(context);
},

View file

@ -3,8 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:grouped_list/sliver_grouped_list.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/browse/extension/providers/extensions_provider.dart';
import 'package:mangayomi/modules/browse/extension/providers/fetch_anime_sources.dart';
import 'package:mangayomi/modules/browse/extension/providers/fetch_manga_sources.dart';
import 'package:mangayomi/services/fetch_anime_sources.dart';
import 'package:mangayomi/services/fetch_manga_sources.dart';
import 'package:mangayomi/modules/widgets/progress_center.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/sources/source_test.dart';

View file

@ -1,5 +1,5 @@
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/source_preference.dart';
import 'package:mangayomi/eval/dart/model/source_preference.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/services/get_source_preference.dart';

View file

@ -0,0 +1,331 @@
import 'package:flutter/material.dart';
import 'package:mangayomi/eval/dart/model/m_bridge.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
class CreateExtension extends StatefulWidget {
const CreateExtension({super.key});
@override
State<CreateExtension> createState() => _CreateExtensionState();
}
class _CreateExtensionState extends State<CreateExtension> {
bool _isManga = false;
bool _isNsfw = false;
String _name = "";
String _lang = "";
String _baseUrl = "";
String _apiUrl = "";
String _iconUrl = "";
int _sourceTypeIndex = 0;
int _languageIndex = 0;
final List<String> _sourceTypes = ["single", "multi", "torrent"];
final List<String> _languages = ["Dart", "JavaScript"];
SourceCodeLanguage _sourceCodeLanguage = SourceCodeLanguage.dart;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Create Extension"),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 17),
child: Row(
children: [
const Text("Choose extension language"),
const SizedBox(width: 20),
Flexible(
child: DropdownButton(
icon: const Icon(Icons.keyboard_arrow_down),
isExpanded: true,
value: _languageIndex,
hint: Text(_languages[_languageIndex],
style: const TextStyle(fontSize: 13)),
items: _languages
.map((e) => DropdownMenuItem(
value: _languages.indexOf(e),
child: Text(e,
style: const TextStyle(fontSize: 13)),
))
.toList(),
onChanged: (v) {
setState(() {
if (v == 0) {
_sourceCodeLanguage = SourceCodeLanguage.dart;
} else {
_sourceCodeLanguage =
SourceCodeLanguage.javascript;
}
_languageIndex = v!;
});
},
),
),
],
),
),
_textEditing("Name", context, "ex: myAnime", (v) {
setState(() {
_name = v;
});
}),
_textEditing("Lang", context, "ex: en", (v) {
setState(() {
_lang = v;
});
}),
_textEditing("BaseUrl", context, "ex: https://example.com",
(v) {
setState(() {
_baseUrl = v;
});
}),
_textEditing(
"ApiUrl (optional)", context, "ex: https://api.example.com",
(v) {
setState(() {
_apiUrl = v;
});
}),
_textEditing("iconUrl", context, "Source icon url", (v) {
setState(() {
_iconUrl = v;
});
}),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 17),
child: Row(
children: [
const Text("Type"),
const SizedBox(width: 20),
Flexible(
child: DropdownButton(
icon: const Icon(Icons.keyboard_arrow_down),
isExpanded: true,
value: _sourceTypeIndex,
hint: Text(_sourceTypes[_sourceTypeIndex],
style: const TextStyle(fontSize: 13)),
items: _sourceTypes
.map((e) => DropdownMenuItem(
value: _sourceTypes.indexOf(e),
child: Text(e,
style: const TextStyle(fontSize: 13)),
))
.toList(),
onChanged: (v) {
setState(() {
_sourceTypeIndex = v!;
});
},
),
),
],
),
),
SwitchListTile(
title: const Text("isNsfw"),
value: _isNsfw,
onChanged: (value) => setState(() {
_isNsfw = value;
}),
),
SwitchListTile(
title: const Text("isManga"),
value: _isManga,
onChanged: (value) => setState(() {
_isManga = value;
}),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
if (_name.isNotEmpty &&
_lang.isNotEmpty &&
_baseUrl.isNotEmpty &&
_iconUrl.isNotEmpty) {
try {
final id =
_sourceCodeLanguage == SourceCodeLanguage.dart
? 'mangayomi-$_lang.$_name'.hashCode
: 'mangayomi-js-$_lang.$_name'.hashCode;
final checkIfExist = isar.sources.getSync(id);
if (checkIfExist == null) {
Source source = Source(
id: id,
name: _name,
lang: _lang,
baseUrl: _baseUrl,
apiUrl: _apiUrl,
iconUrl: _iconUrl,
typeSource: _sourceTypes[_sourceTypeIndex],
isManga: _isManga,
isAdded: true,
isActive: true,
version: "0.0.1",
isNsfw: _isNsfw)
..sourceCodeLanguage = _sourceCodeLanguage;
source = source
..sourceCode = _sourceCodeLanguage ==
SourceCodeLanguage.dart
? _dartTemplate
: _jsSample(source);
isar.writeTxnSync(
() => isar.sources.putSync(source));
Navigator.pop(context);
botToast("Source created successfully");
} else {
botToast("Source already exists");
}
} catch (e) {
botToast("Error when creating source");
}
}
},
child: Text(context.l10n.save)),
)
],
),
),
));
}
}
Widget _textEditing(String label, BuildContext context, String hintText,
void Function(String)? onChanged) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 17, vertical: 5),
child: TextFormField(
keyboardType: TextInputType.number,
onChanged: onChanged,
decoration: InputDecoration(
hintText: hintText,
labelText: label,
isDense: true,
filled: true,
fillColor: Colors.transparent,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: context.secondaryColor)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: context.secondaryColor)),
border: OutlineInputBorder(
borderSide: BorderSide(color: context.secondaryColor))),
),
);
}
const _dartTemplate = r'''
import 'package:mangayomi/bridge_lib.dart';
import 'dart:convert';
class TestSource extends MProvider {
TestSource({required this.source});
MSource source;
final Client client = Client(source);
@override
bool get supportsLatest => true;
@override
Future<MPages> getPopular(int page) async {
// TODO: implement
}
@override
Future<MPages> getLatestUpdates(int page) async {
// TODO: implement
}
@override
Future<MPages> search(String query, int page, FilterList filterList) async {
// TODO: implement
}
@override
Future<MManga> getDetail(String url) async {
// TODO: implement
}
// For anime episode video list
@override
Future<List<MVideo>> getVideoList(String url) async {
// TODO: implement
}
// For manga chapter pages
@override
Future<List<String>> getPageList(String url) async{
// TODO: implement
}
@override
List<dynamic> getFilterList() {
// TODO: implement
}
@override
List<dynamic> getSourcePreferences() {
// TODO: implement
}
}
TestSource main(MSource source) {
return TestSource(source:source);
}''';
String _jsSample(Source source) => '''
const sources = [{
"name": "${source.name}",
"lang": "${source.lang}",
"baseUrl": "${source.baseUrl}",
"apiUrl": "${source.apiUrl}",
"iconUrl": "${source.iconUrl}",
"typeSource": "${source.typeSource}",
"isManga": ${source.isManga},
"isNsfw": ${source.isNsfw},
"version": "${source.version}",
"apiUrl": "",
"dateFormat": "",
"dateFormatLocale": "",
"pkgName": "en/madara"
}];
class DefaultExtension extends MProvider {
async getPopular(page) {
throw new Error("getPopular not implemented");
}
async getLatestUpdates(page) {
throw new Error("getLatestUpdates not implemented");
}
async search(query, page, filters) {
throw new Error("search not implemented");
}
async getDetail(url) {
throw new Error("getDetail not implemented");
}
// For anime episode video list
async getVideoList(url) {
throw new Error("getVideoList not implemented");
}
// For manga chapter pages
async getPageList() {
throw new Error("getPageList not implemented");
}
getFilterList() {
throw new Error("getFilterList not implemented");
}
getSourcePreferences() {
throw new Error("getSourcePreferences not implemented");
}
}
''';

View file

@ -1,12 +1,13 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/browse/extension/providers/fetch_anime_sources.dart';
import 'package:mangayomi/modules/browse/extension/providers/fetch_manga_sources.dart';
import 'package:mangayomi/services/fetch_anime_sources.dart';
import 'package:mangayomi/services/fetch_manga_sources.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/cached_network.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/utils/language.dart';
class ExtensionListTileWidget extends ConsumerStatefulWidget {
@ -68,20 +69,19 @@ class _ExtensionListTileWidgetState
borderRadius: BorderRadius.circular(5)),
child: widget.source.iconUrl!.isEmpty
? const Icon(Icons.source_outlined)
: CachedNetworkImage(
: cachedNetworkImage(
imageUrl: widget.source.iconUrl!,
fit: BoxFit.contain,
width: 37,
height: 37,
errorWidget: (context, url, error) {
return const SizedBox(
width: 37,
height: 37,
child: Center(
child: Icon(Icons.source_outlined),
),
);
},
errorWidget: const SizedBox(
width: 37,
height: 37,
child: Center(
child: Icon(Icons.source_outlined),
),
),
headers: {},
),
),
title: Text(widget.source.name!),
@ -91,42 +91,46 @@ class _ExtensionListTileWidgetState
Text(completeLanguageName(widget.source.lang!.toLowerCase()),
style:
const TextStyle(fontWeight: FontWeight.w300, fontSize: 12)),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const SizedBox(width: 4),
Text(widget.source.version!,
style: const TextStyle(
fontWeight: FontWeight.w300, fontSize: 10)),
if (widget.source.isNsfw!)
Row(
children: [
const SizedBox(
width: 2,
),
SizedBox(
height: 15,
child: Container(
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.7),
borderRadius: BorderRadius.circular(5)),
child: const Center(
child: Padding(
padding: EdgeInsets.all(3),
child: Text(
"NSFW",
style: TextStyle(
fontSize: 6,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
)),
),
],
const SizedBox(width: 4),
Text(widget.source.version!,
style:
const TextStyle(fontWeight: FontWeight.w300, fontSize: 12)),
if (widget.source.isNsfw!)
Row(
children: [
const SizedBox(
width: 2,
),
],
)
SizedBox(
height: 15,
child: Container(
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.7),
borderRadius: BorderRadius.circular(5)),
child: const Center(
child: Padding(
padding: EdgeInsets.all(3),
child: Text(
"NSFW",
style: TextStyle(
fontSize: 6,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
)),
),
],
),
if (widget.source.isObsolete ?? false)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text("OBSOLETE",
style: TextStyle(
color: context.primaryColor,
fontWeight: FontWeight.bold,
fontSize: 12)),
)
],
),
trailing: TextButton(

View file

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:mangayomi/eval/model/source_preference.dart';
import 'package:mangayomi/eval/dart/model/source_preference.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/browse/extension/providers/extension_preferences_providers.dart';
import 'package:mangayomi/modules/manga/detail/widgets/chapter_filter_list_tile_widget.dart';

View file

@ -2,8 +2,8 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/m_manga.dart';
import 'package:mangayomi/eval/model/m_pages.dart';
import 'package:mangayomi/eval/dart/model/m_manga.dart';
import 'package:mangayomi/eval/dart/model/m_pages.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/modules/manga/home/manga_home_screen.dart';

View file

@ -1,4 +1,3 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:grouped_list/grouped_list.dart';
@ -7,6 +6,7 @@ import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/cached_network.dart';
import 'package:mangayomi/utils/language.dart';
class SourcesFilterScreen extends ConsumerWidget {
@ -86,20 +86,18 @@ class SourcesFilterScreen extends ConsumerWidget {
borderRadius: BorderRadius.circular(5)),
child: element.iconUrl!.isEmpty
? const Icon(Icons.source_outlined)
: CachedNetworkImage(
: cachedNetworkImage(
imageUrl: element.iconUrl!,
fit: BoxFit.contain,
width: 37,
height: 37,
errorWidget: (context, url, error) {
return const SizedBox(
width: 37,
height: 37,
child: Center(
child: Icon(Icons.source_outlined),
),
);
},
errorWidget: const SizedBox(
width: 37,
height: 37,
child: Center(
child: Icon(Icons.source_outlined),
),
),
),
),
onChanged: (bool? value) {

View file

@ -6,9 +6,9 @@ import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/sources/source_test.dart';
import 'package:mangayomi/utils/cached_network.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/utils/language.dart';
import 'package:cached_network_image/cached_network_image.dart';
class SourceListTile extends StatelessWidget {
final bool isManga;
@ -46,20 +46,18 @@ class SourceListTile extends StatelessWidget {
borderRadius: BorderRadius.circular(5)),
child: source.iconUrl!.isEmpty
? const Icon(Icons.source_outlined)
: CachedNetworkImage(
: cachedNetworkImage(
imageUrl: source.iconUrl!,
fit: BoxFit.contain,
width: 37,
height: 37,
errorWidget: (context, url, error) {
return const SizedBox(
width: 37,
height: 37,
child: Center(
child: Icon(Icons.source_outlined),
),
);
},
errorWidget: const SizedBox(
width: 37,
height: 37,
child: Center(
child: Icon(Icons.source_outlined),
),
),
),
),
subtitle: Row(

View file

@ -1,12 +1,14 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:io';
import 'dart:math';
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/eval/dart/model/m_bridge.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/category.dart';
import 'package:mangayomi/models/chapter.dart';
@ -692,6 +694,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
language: language,
mangaIdsList: mangaIdsList,
localSource: localSource,
isManga: widget.isManga,
),
);
}
@ -765,6 +768,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
language: language,
mangaIdsList: mangaIdsList,
localSource: localSource,
isManga: widget.isManga,
),
);
}
@ -1345,7 +1349,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 20, top: 10),
padding: const EdgeInsets.only(left: 20, right: 20, top: 10),
child: Row(
children: [
Text(l10n.display_mode),
@ -1408,8 +1412,63 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
// ),
).toList()),
),
Consumer(
builder: (context, ref, child) {
final gridSize = ref.watch(libraryGridSizeStateProvider(
isManga: widget.isManga)) ??
0;
return Padding(
padding: const EdgeInsets.only(left: 8, right: 8, top: 10),
child: Row(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
children: [
Text(context.l10n.grid_size),
Text(gridSize == 0
? context.l10n.default0
: context.l10n.n_per_row(gridSize.toString()))
],
),
),
Flexible(
flex: 7,
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
overlayShape: const RoundSliderOverlayShape(
overlayRadius: 5.0),
),
child: Slider(
min: 0.0,
max: 7,
divisions: max(7, 0),
value: gridSize.toDouble(),
onChanged: (value) {
HapticFeedback.vibrate();
ref
.read(libraryGridSizeStateProvider(
isManga: widget.isManga)
.notifier)
.set(value.toInt());
},
onChangeEnd: (value) {
ref
.read(libraryGridSizeStateProvider(
isManga: widget.isManga)
.notifier)
.set(value.toInt(), end: true);
},
),
),
)
],
),
);
},
),
Padding(
padding: const EdgeInsets.only(left: 20, top: 10),
padding: const EdgeInsets.only(left: 20, right: 20, top: 10),
child: Row(
children: [
Text(l10n.badges),
@ -1469,7 +1528,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
),
),
Padding(
padding: const EdgeInsets.only(left: 20, top: 10),
padding: const EdgeInsets.only(left: 20, right: 20, top: 10),
child: Row(
children: [Text(l10n.tabs)],
),

View file

@ -42,6 +42,35 @@ class LibraryDisplayTypeState extends _$LibraryDisplayTypeState {
}
}
@riverpod
class LibraryGridSizeState extends _$LibraryGridSizeState {
@override
int? build({required bool isManga}) {
return isManga ? settings.mangaGridSize : settings.animeGridSize;
}
Settings get settings {
return isar.settings.getSync(227)!;
}
void set(int? value, {bool end = false}) {
Settings appSettings = Settings();
state = value;
if (end) {
if (isManga) {
appSettings = settings..mangaGridSize = value;
} else {
appSettings = settings..animeGridSize = value;
}
isar.writeTxnSync(() {
isar.settings.putSync(appSettings);
});
}
}
}
@riverpod
class MangaFilterDownloadedState extends _$MangaFilterDownloadedState {
@override

View file

@ -196,6 +196,151 @@ class _LibraryDisplayTypeStateProviderElement
Settings get settings => (origin as LibraryDisplayTypeStateProvider).settings;
}
String _$libraryGridSizeStateHash() =>
r'a4e55ef92f9387c2588679c5e2f23ef689e5d593';
abstract class _$LibraryGridSizeState
extends BuildlessAutoDisposeNotifier<int?> {
late final bool isManga;
int? build({
required bool isManga,
});
}
/// See also [LibraryGridSizeState].
@ProviderFor(LibraryGridSizeState)
const libraryGridSizeStateProvider = LibraryGridSizeStateFamily();
/// See also [LibraryGridSizeState].
class LibraryGridSizeStateFamily extends Family<int?> {
/// See also [LibraryGridSizeState].
const LibraryGridSizeStateFamily();
/// See also [LibraryGridSizeState].
LibraryGridSizeStateProvider call({
required bool isManga,
}) {
return LibraryGridSizeStateProvider(
isManga: isManga,
);
}
@override
LibraryGridSizeStateProvider getProviderOverride(
covariant LibraryGridSizeStateProvider provider,
) {
return call(
isManga: provider.isManga,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'libraryGridSizeStateProvider';
}
/// See also [LibraryGridSizeState].
class LibraryGridSizeStateProvider
extends AutoDisposeNotifierProviderImpl<LibraryGridSizeState, int?> {
/// See also [LibraryGridSizeState].
LibraryGridSizeStateProvider({
required bool isManga,
}) : this._internal(
() => LibraryGridSizeState()..isManga = isManga,
from: libraryGridSizeStateProvider,
name: r'libraryGridSizeStateProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$libraryGridSizeStateHash,
dependencies: LibraryGridSizeStateFamily._dependencies,
allTransitiveDependencies:
LibraryGridSizeStateFamily._allTransitiveDependencies,
isManga: isManga,
);
LibraryGridSizeStateProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.isManga,
}) : super.internal();
final bool isManga;
@override
int? runNotifierBuild(
covariant LibraryGridSizeState notifier,
) {
return notifier.build(
isManga: isManga,
);
}
@override
Override overrideWith(LibraryGridSizeState Function() create) {
return ProviderOverride(
origin: this,
override: LibraryGridSizeStateProvider._internal(
() => create()..isManga = isManga,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
isManga: isManga,
),
);
}
@override
AutoDisposeNotifierProviderElement<LibraryGridSizeState, int?>
createElement() {
return _LibraryGridSizeStateProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is LibraryGridSizeStateProvider && other.isManga == isManga;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, isManga.hashCode);
return _SystemHash.finish(hash);
}
}
mixin LibraryGridSizeStateRef on AutoDisposeNotifierProviderRef<int?> {
/// The parameter `isManga` of this provider.
bool get isManga;
}
class _LibraryGridSizeStateProviderElement
extends AutoDisposeNotifierProviderElement<LibraryGridSizeState, int?>
with LibraryGridSizeStateRef {
_LibraryGridSizeStateProviderElement(super.provider);
@override
bool get isManga => (origin as LibraryGridSizeStateProvider).isManga;
}
String _$mangaFilterDownloadedStateHash() =>
r'9c07e64580061bf2cbf892ef679274913aaa3b20';

View file

@ -1,5 +1,4 @@
import 'dart:typed_data';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart';
@ -9,6 +8,7 @@ import 'package:mangayomi/modules/history/providers/isar_providers.dart';
import 'package:mangayomi/modules/library/providers/library_state_provider.dart';
import 'package:mangayomi/modules/manga/reader/providers/push_router.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/utils/constant.dart';
import 'package:mangayomi/utils/headers.dart';
@ -20,7 +20,7 @@ import 'package:mangayomi/modules/widgets/gridview_widget.dart';
import 'package:mangayomi/modules/widgets/manga_image_card_widget.dart';
import 'package:mangayomi/modules/widgets/progress_center.dart';
class LibraryGridViewWidget extends StatelessWidget {
class LibraryGridViewWidget extends StatefulWidget {
final bool isCoverOnlyGrid;
final bool isComfortableGrid;
final List<int> mangaIdsList;
@ -29,6 +29,7 @@ class LibraryGridViewWidget extends StatelessWidget {
final bool downloadedChapter;
final bool continueReaderBtn;
final bool localSource;
final bool isManga;
const LibraryGridViewWidget(
{super.key,
required this.entriesManga,
@ -38,271 +39,308 @@ class LibraryGridViewWidget extends StatelessWidget {
required this.downloadedChapter,
required this.continueReaderBtn,
required this.mangaIdsList,
required this.localSource});
required this.localSource,
required this.isManga});
@override
State<LibraryGridViewWidget> createState() => _LibraryGridViewWidgetState();
}
class _LibraryGridViewWidgetState extends State<LibraryGridViewWidget> {
@override
Widget build(BuildContext context) {
return GridViewWidget(
childAspectRatio: isComfortableGrid ? 0.642 : 0.69,
itemCount: entriesManga.length,
itemBuilder: (context, index) {
final entry = entriesManga[index];
return Consumer(builder: (context, ref, child) {
bool isLocalArchive = entry.isLocalArchive ?? false;
final isLongPressed = ref.watch(isLongPressedMangaStateProvider);
return Padding(
padding: const EdgeInsets.all(2),
child: CoverViewWidget(
isLongPressed: mangaIdsList.contains(entry.id),
bottomTextWidget: BottomTextWidget(
maxLines: 1,
text: entry.name!,
isComfortableGrid: isComfortableGrid,
),
isComfortableGrid: isComfortableGrid,
image: entry.customCoverImage != null
? MemoryImage(entry.customCoverImage as Uint8List)
as ImageProvider
: CachedNetworkImageProvider(
toImgUrl(entry.customCoverFromTracker ?? entry.imageUrl!),
headers: entry.isLocalArchive!
? null
: ref.watch(headersProvider(
source: entry.source!, lang: entry.lang!)),
return Consumer(builder: (context, ref, child) {
final isLongPressed = ref.watch(isLongPressedMangaStateProvider);
final isManga = widget.isManga;
final gridSize =
ref.watch(libraryGridSizeStateProvider(isManga: isManga));
return GridViewWidget(
gridSize: gridSize,
childAspectRatio: widget.isComfortableGrid ? 0.642 : 0.69,
itemCount: widget.entriesManga.length,
itemBuilder: (context, index) {
final entry = widget.entriesManga[index];
return Builder(builder: (context) {
bool isLocalArchive = entry.isLocalArchive ?? false;
return Padding(
padding: const EdgeInsets.all(2),
child: CoverViewWidget(
isLongPressed: widget.mangaIdsList.contains(entry.id),
bottomTextWidget: BottomTextWidget(
maxLines: 1,
text: entry.name!,
isComfortableGrid: widget.isComfortableGrid,
),
onTap: () {
if (isLongPressed) {
ref.read(mangasListStateProvider.notifier).update(entry);
} else {
pushToMangaReaderDetail(
archiveId: isLocalArchive ? entry.id : null,
context: context,
lang: entry.lang!,
mangaM: entry,
source: entry.source!);
}
},
onLongPress: () {
if (!isLongPressed) {
ref.read(mangasListStateProvider.notifier).update(entry);
ref
.read(isLongPressedMangaStateProvider.notifier)
.update(!isLongPressed);
} else {
ref.read(mangasListStateProvider.notifier).update(entry);
}
},
children: [
Stack(
children: [
Positioned(
top: 0,
left: 0,
child: Padding(
padding: const EdgeInsets.all(5),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3),
color: context.primaryColor,
),
child: Row(
children: [
if (localSource && isLocalArchive)
Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(3),
bottomLeft: Radius.circular(3)),
color: Theme.of(context).hintColor,
),
child: const Padding(
padding:
EdgeInsets.only(left: 3, right: 3),
child: Text(
"Local",
style: TextStyle(color: Colors.white),
),
),
),
if (downloadedChapter)
Padding(
padding: const EdgeInsets.only(right: 5),
child: Consumer(
builder: (context, ref, child) {
List nbrDown = [];
isar.txnSync(() {
for (var i = 0;
i < entry.chapters.length;
i++) {
final entries = isar.downloads
.filter()
.idIsNotNull()
.chapterIdEqualTo(entry.chapters
.toList()[i]
.id)
.findAllSync();
if (entries.isNotEmpty &&
entries.first.isDownload!) {
nbrDown.add(entries.first);
}
}
});
if (nbrDown.isNotEmpty) {
return Container(
decoration: BoxDecoration(
borderRadius:
const BorderRadius.only(
topLeft:
Radius.circular(3),
bottomLeft:
Radius.circular(3)),
color:
Theme.of(context).hintColor,
),
child: Padding(
padding: const EdgeInsets.only(
left: 3, right: 3),
child: Text(
nbrDown.length.toString(),
style: const TextStyle(
color: Colors.white),
),
),
);
} else {
return Container();
}
},
),
),
Padding(
padding: const EdgeInsets.only(right: 3),
child: Text(
entry.chapters.length.toString(),
style: const TextStyle(color: Colors.white),
),
),
],
),
isComfortableGrid: widget.isComfortableGrid,
image: entry.customCoverImage != null
? MemoryImage(entry.customCoverImage as Uint8List)
as ImageProvider
: CustomExtendedNetworkImageProvider(
toImgUrl(entry.customCoverFromTracker ??
entry.imageUrl!),
headers: entry.isLocalArchive!
? null
: ref.watch(headersProvider(
source: entry.source!, lang: entry.lang!)),
),
)),
if (language && entry.lang!.isNotEmpty)
Positioned(
top: 0,
right: 0,
child: Padding(
padding: const EdgeInsets.all(5),
child: Container(
color: context.themeData.cardColor,
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(3),
bottomLeft: Radius.circular(3)),
color: Theme.of(context).hintColor,
),
child: Padding(
padding:
const EdgeInsets.only(left: 3, right: 3),
child: Text(
entry.lang!.toUpperCase(),
style: const TextStyle(color: Colors.white),
onTap: () {
if (isLongPressed) {
ref
.read(mangasListStateProvider.notifier)
.update(entry);
} else {
pushToMangaReaderDetail(
archiveId: isLocalArchive ? entry.id : null,
context: context,
lang: entry.lang!,
mangaM: entry,
source: entry.source!);
}
},
onLongPress: () {
if (!isLongPressed) {
ref
.read(mangasListStateProvider.notifier)
.update(entry);
ref
.read(isLongPressedMangaStateProvider.notifier)
.update(!isLongPressed);
} else {
ref
.read(mangasListStateProvider.notifier)
.update(entry);
}
},
children: [
Stack(
children: [
Positioned(
top: 0,
left: 0,
child: Padding(
padding: const EdgeInsets.all(5),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3),
color: context.primaryColor,
),
child: Row(
children: [
if (widget.localSource && isLocalArchive)
Container(
decoration: BoxDecoration(
borderRadius:
const BorderRadius.only(
topLeft: Radius.circular(3),
bottomLeft:
Radius.circular(3)),
color: Theme.of(context).hintColor,
),
child: const Padding(
padding: EdgeInsets.only(
left: 3, right: 3),
child: Text(
"Local",
style: TextStyle(
color: Colors.white),
),
),
),
if (widget.downloadedChapter)
Padding(
padding:
const EdgeInsets.only(right: 5),
child: Consumer(
builder: (context, ref, child) {
List nbrDown = [];
isar.txnSync(() {
for (var i = 0;
i < entry.chapters.length;
i++) {
final entries = isar.downloads
.filter()
.idIsNotNull()
.chapterIdEqualTo(entry
.chapters
.toList()[i]
.id)
.findAllSync();
if (entries.isNotEmpty &&
entries
.first.isDownload!) {
nbrDown.add(entries.first);
}
}
});
if (nbrDown.isNotEmpty) {
return Container(
decoration: BoxDecoration(
borderRadius:
const BorderRadius.only(
topLeft:
Radius.circular(
3),
bottomLeft:
Radius.circular(
3)),
color: Theme.of(context)
.hintColor,
),
child: Padding(
padding:
const EdgeInsets.only(
left: 3, right: 3),
child: Text(
nbrDown.length.toString(),
style: const TextStyle(
color: Colors.white),
),
),
);
} else {
return Container();
}
},
),
),
Padding(
padding:
const EdgeInsets.only(right: 3),
child: Text(
entry.chapters.length.toString(),
style: const TextStyle(
color: Colors.white),
),
),
],
),
),
),
),
)),
],
),
if (!isComfortableGrid && !isCoverOnlyGrid)
BottomTextWidget(text: entry.name!),
if (continueReaderBtn)
Positioned(
bottom: 0,
right: 0,
child: Padding(
padding: const EdgeInsets.all(9),
child: Consumer(
builder: (context, ref, child) {
final history = ref.watch(
getAllHistoryStreamProvider(
isManga: entry.isManga!));
return history.when(
data: (data) {
final incognitoMode =
ref.watch(incognitoModeStateProvider);
final entries = data
.where((element) =>
element.mangaId == entry.id)
.toList();
if (entries.isNotEmpty && !incognitoMode) {
return GestureDetector(
onTap: () {
pushMangaReaderView(
context: context,
chapter: entries.first.chapter.value!,
);
},
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(5),
color: context.primaryColor
.withOpacity(0.9),
),
child: const Padding(
padding: EdgeInsets.all(7),
child: Icon(
Icons.play_arrow,
size: 19,
color: Colors.white,
)),
),
);
}
return GestureDetector(
onTap: () {
pushMangaReaderView(
context: context,
chapter: entry.chapters
.toList()
.reversed
.toList()
.last);
},
)),
if (widget.language && entry.lang!.isNotEmpty)
Positioned(
top: 0,
right: 0,
child: Padding(
padding: const EdgeInsets.all(5),
child: Container(
color: context.themeData.cardColor,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: context.primaryColor
.withOpacity(0.9),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(3),
bottomLeft: Radius.circular(3)),
color: Theme.of(context).hintColor,
),
child: Padding(
padding: const EdgeInsets.only(
left: 3, right: 3),
child: Text(
entry.lang!.toUpperCase(),
style: const TextStyle(
color: Colors.white),
),
),
child: const Padding(
padding: EdgeInsets.all(7),
child: Icon(
Icons.play_arrow,
size: 19,
color: Colors.white,
)),
),
);
},
error: (Object error, StackTrace stackTrace) {
return ErrorText(error);
},
loading: () {
return const ProgressCenter();
},
);
},
)))
],
),
);
});
},
);
),
)),
],
),
if (!widget.isComfortableGrid && !widget.isCoverOnlyGrid)
BottomTextWidget(text: entry.name!),
if (widget.continueReaderBtn)
Positioned(
bottom: 0,
right: 0,
child: Padding(
padding: const EdgeInsets.all(9),
child: Consumer(
builder: (context, ref, child) {
final history = ref.watch(
getAllHistoryStreamProvider(
isManga: entry.isManga!));
return history.when(
data: (data) {
final incognitoMode = ref
.watch(incognitoModeStateProvider);
final entries = data
.where((element) =>
element.mangaId == entry.id)
.toList();
if (entries.isNotEmpty &&
!incognitoMode) {
return GestureDetector(
onTap: () {
pushMangaReaderView(
context: context,
chapter: entries
.first.chapter.value!,
);
},
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(5),
color: context.primaryColor
.withOpacity(0.9),
),
child: const Padding(
padding: EdgeInsets.all(7),
child: Icon(
Icons.play_arrow,
size: 19,
color: Colors.white,
)),
),
);
}
return GestureDetector(
onTap: () {
pushMangaReaderView(
context: context,
chapter: entry.chapters
.toList()
.reversed
.toList()
.last);
},
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(5),
color: context.primaryColor
.withOpacity(0.9),
),
child: const Padding(
padding: EdgeInsets.all(7),
child: Icon(
Icons.play_arrow,
size: 19,
color: Colors.white,
)),
),
);
},
error: (Object error,
StackTrace stackTrace) {
return ErrorText(error);
},
loading: () {
return const ProgressCenter();
},
);
},
)))
],
),
);
});
},
);
});
}
}

View file

@ -1,5 +1,4 @@
import 'dart:typed_data';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart';
@ -9,6 +8,7 @@ import 'package:mangayomi/modules/history/providers/isar_providers.dart';
import 'package:mangayomi/modules/library/providers/library_state_provider.dart';
import 'package:mangayomi/modules/manga/reader/providers/push_router.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/utils/constant.dart';
import 'package:mangayomi/utils/headers.dart';
@ -98,7 +98,7 @@ class LibraryListViewWidget extends StatelessWidget {
image: entry.customCoverImage != null
? MemoryImage(entry.customCoverImage
as Uint8List) as ImageProvider
: CachedNetworkImageProvider(
: CustomExtendedNetworkImageProvider(
toImgUrl(
entry.customCoverFromTracker ??
entry.imageUrl!),

View file

@ -6,8 +6,8 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/browse/extension/providers/fetch_anime_sources.dart';
import 'package:mangayomi/modules/browse/extension/providers/fetch_manga_sources.dart';
import 'package:mangayomi/services/fetch_anime_sources.dart';
import 'package:mangayomi/services/fetch_manga_sources.dart';
import 'package:mangayomi/modules/main_view/providers/migration.dart';
import 'package:mangayomi/modules/more/about/providers/check_for_update.dart';
import 'package:mangayomi/modules/more/backup_and_restore/providers/auto_backup.dart';

View file

@ -1,6 +1,5 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:draggable_menu/draggable_menu.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
@ -8,7 +7,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/eval/dart/model/m_bridge.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/download.dart';
@ -24,6 +23,7 @@ import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provi
import 'package:mangayomi/modules/more/settings/appearance/providers/pure_black_dark_mode_state_provider.dart';
import 'package:mangayomi/modules/more/settings/track/widgets/track_listile.dart';
import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart';
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
import 'package:mangayomi/modules/widgets/draggable_scroll_bar.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/providers/storage_provider.dart';
@ -1366,7 +1366,7 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
final imageProvider = widget.manga!.customCoverImage != null
? MemoryImage(widget.manga!.customCoverImage as Uint8List)
as ImageProvider
: CachedNetworkImageProvider(
: CustomExtendedNetworkImageProvider(
toImgUrl(widget.manga!.customCoverFromTracker ??
widget.manga!.imageUrl!),
headers: widget.manga!.isLocalArchive!

View file

@ -1,5 +1,5 @@
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/eval/model/m_manga.dart';
import 'package:mangayomi/eval/dart/model/m_bridge.dart';
import 'package:mangayomi/eval/dart/model/m_manga.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';

View file

@ -1,10 +1,10 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:draggable_menu/draggable_menu.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/models/track.dart';
import 'package:mangayomi/models/track_search.dart';
import 'package:mangayomi/modules/manga/detail/providers/track_state_providers.dart';
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
import 'package:mangayomi/modules/widgets/progress_center.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
@ -84,8 +84,9 @@ class _TrackerWidgetSearchState extends ConsumerState<TrackerWidgetSearch> {
height: 120,
width: 80,
fit: BoxFit.cover,
image: CachedNetworkImageProvider(
tracks![index].coverUrl!),
image:
CustomExtendedNetworkImageProvider(
tracks![index].coverUrl!),
),
),
const SizedBox(

View file

@ -1,5 +1,4 @@
import 'dart:io';
import 'package:background_downloader/background_downloader.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@ -11,6 +10,7 @@ import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/modules/manga/download/providers/download_provider.dart';
import 'package:mangayomi/services/background_downloader/background_downloader.dart';
import 'package:mangayomi/utils/global_style.dart';
import 'package:share_plus/share_plus.dart';

View file

@ -1,5 +1,5 @@
import 'dart:io';
import 'package:background_downloader/background_downloader.dart';
import 'package:mangayomi/services/background_downloader/background_downloader.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
@ -10,6 +10,7 @@ import 'package:mangayomi/modules/more/settings/downloads/providers/downloads_st
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/services/get_video_list.dart';
import 'package:mangayomi/services/get_chapter_pages.dart';
import 'package:mangayomi/services/http/m_client.dart';
import 'package:mangayomi/utils/extensions/string_extensions.dart';
import 'package:mangayomi/utils/headers.dart';
import 'package:mangayomi/utils/reg_exp_matcher.dart';
@ -132,6 +133,17 @@ Future<List<String>> downloadChapter(
if (!(await path3.exists())) {
await path3.create();
}
final cookie = MClient.getCookiesPref(pageUrls[index]);
final headers = isManga
? ref.watch(
headersProvider(source: manga.source!, lang: manga.lang!))
: videoHeader;
if (cookie.isNotEmpty) {
final userAgent = isar.settings.getSync(227)!.userAgent!;
headers.addAll(cookie);
headers[HttpHeaders.userAgentHeader] = userAgent;
}
if (isManga) {
if ((await path.exists())) {
if (await File("${path.path}" "${padIndex(index + 1)}.jpg")
@ -139,8 +151,7 @@ Future<List<String>> downloadChapter(
} else {
tasks.add(DownloadTask(
taskId: pageUrls[index],
headers: ref.watch(headersProvider(
source: manga.source!, lang: manga.lang!)),
headers: headers,
url: pageUrls[index].trim().trimLeft().trimRight(),
filename: "${padIndex(index + 1)}.jpg",
baseDirectory: BaseDirectory.temporary,
@ -157,8 +168,7 @@ Future<List<String>> downloadChapter(
} else {
tasks.add(DownloadTask(
taskId: pageUrls[index],
headers: ref.watch(headersProvider(
source: manga.source!, lang: manga.lang!)),
headers: headers,
url: pageUrls[index].trim().trimLeft().trimRight(),
filename: "${padIndex(index + 1)}.jpg",
baseDirectory: BaseDirectory.temporary,
@ -175,7 +185,7 @@ Future<List<String>> downloadChapter(
} else {
tasks.add(DownloadTask(
taskId: pageUrls[index],
headers: videoHeader,
headers: headers,
url: pageUrls[index].trim().trimLeft().trimRight(),
filename: "${chapter.name}.mp4",
baseDirectory: BaseDirectory.temporary,
@ -191,7 +201,7 @@ Future<List<String>> downloadChapter(
} else {
tasks.add(DownloadTask(
taskId: pageUrls[index],
headers: videoHeader,
headers: headers,
url: pageUrls[index].trim().trimLeft().trimRight(),
filename: "${chapter.name}.mp4",
baseDirectory: BaseDirectory.temporary,
@ -224,7 +234,7 @@ Future<List<String>> downloadChapter(
} else {
savePageUrls();
if (isManga) {
await FileDownloader().downloadBatch(
await FileDownloader(hasGPServices: hasGPServices).downloadBatch(
tasks,
batchProgressCallback: (succeeded, failed) async {
if (succeeded == tasks.length) {
@ -279,7 +289,7 @@ Future<List<String>> downloadChapter(
},
);
} else {
await FileDownloader().download(
await FileDownloader(hasGPServices: hasGPServices).download(
tasks.first,
onProgress: (progress) async {
bool isEmpty = isar.downloads

View file

@ -6,7 +6,7 @@ part of 'download_provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$downloadChapterHash() => r'dcb0ade7f80a1ec593667e7311cdafc2d31222cf';
String _$downloadChapterHash() => r'a8bbd438963157af07b92601c1741195421a3281';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -3,10 +3,11 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/eval/model/m_manga.dart';
import 'package:mangayomi/eval/model/m_pages.dart';
import 'package:mangayomi/eval/dart/model/m_manga.dart';
import 'package:mangayomi/eval/dart/model/m_pages.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/library/providers/library_state_provider.dart';
import 'package:mangayomi/modules/manga/home/providers/state_provider.dart';
import 'package:mangayomi/modules/manga/home/widget/filter_widget.dart';
import 'package:mangayomi/modules/widgets/listview_widget.dart';
@ -73,27 +74,28 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
];
}
late Source source = widget.source;
Future<MPages?> _loadMore() async {
MPages? mangaRes;
if (_isLoading) {
if (widget.source.isFullData!) {
if (source.isFullData!) {
await Future.delayed(const Duration(milliseconds: 500));
_fullDataLength = _fullDataLength + 50;
} else {
if (_selectedIndex == 0 && !_isSearch && _query.isEmpty) {
mangaRes = await ref.watch(getPopularProvider(
source: widget.source,
source: source,
page: _page + 1,
).future);
} else if (_selectedIndex == 1 && !_isSearch && _query.isEmpty) {
mangaRes = await ref.watch(getLatestUpdatesProvider(
source: widget.source,
source: source,
page: _page + 1,
).future);
} else if (_selectedIndex == 2 && (_isSearch && _query.isNotEmpty) ||
_isFiltering) {
mangaRes = await ref.watch(searchProvider(
source: widget.source,
source: source,
query: _query,
page: _page + 1,
filterList: filters)
@ -119,20 +121,19 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
AsyncValue<MPages?>? _getManga;
int _length = 0;
bool _isFiltering = false;
@override
Widget build(BuildContext context) {
final supportsLatest =
ref.watch(supportsLatestProvider(source: widget.source));
final filterList = getFilterList(source: widget.source);
final supportsLatest = ref.watch(supportsLatestProvider(source: source));
final filterList = getFilterList(source: source);
if (_selectedIndex == 2 && (_isSearch && _query.isNotEmpty) ||
_isFiltering) {
_getManga = ref.watch(searchProvider(
source: widget.source, query: _query, page: 1, filterList: filters));
source: source, query: _query, page: 1, filterList: filters));
} else if (_selectedIndex == 1 && !_isSearch && _query.isEmpty) {
_getManga =
ref.watch(getLatestUpdatesProvider(source: widget.source, page: 1));
_getManga = ref.watch(getLatestUpdatesProvider(source: source, page: 1));
} else if (_selectedIndex == 0 && !_isSearch && _query.isEmpty) {
_getManga = ref.watch(getPopularProvider(source: widget.source, page: 1));
_getManga = ref.watch(getPopularProvider(source: source, page: 1));
}
final l10n = context.l10n;
final displayType = ref.watch(mangaHomeDisplayTypeStateProvider);
@ -144,7 +145,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
};
return Scaffold(
appBar: AppBar(
title: _isSearch ? null : Text('${widget.source.name}'),
title: _isSearch ? null : Text('${source.name}'),
leading: !_isSearch ? null : Container(),
actions: [
_isSearch
@ -260,10 +261,10 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
onSelected: (value) async {
if (value == 0) {
final baseUrl =
ref.watch(sourceBaseUrlProvider(source: widget.source));
ref.watch(sourceBaseUrlProvider(source: source));
Map<String, dynamic> data = {
'url': baseUrl,
'sourceId': widget.source.id.toString(),
'sourceId': source.id.toString(),
'title': ''
};
if (Platform.isLinux) {
@ -283,7 +284,13 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
context.push("/mangawebview", extra: data);
}
} else {
context.push('/extension_detail', extra: widget.source);
final res =
await context.push('/extension_detail', extra: source);
if (res != null && mounted) {
setState(() {
source = res as Source;
});
}
}
}),
],
@ -329,7 +336,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
onPressed: () {
setState(() {
filters = getFilterList(
source: widget.source);
source: source);
});
},
child: Text(l10n.reset),
@ -378,7 +385,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
}
_getManga = ref.refresh(searchProvider(
source: widget.source,
source: source,
query: _query,
page: 1,
filterList: filters));
@ -410,6 +417,15 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
),
body: _getManga!.when(
data: (data) {
if (_hasNextPage) {
if (!data!.hasNextPage) {
if (mounted) {
setState(() {
_hasNextPage = false;
});
}
}
}
if (_mangaList.isEmpty && data!.list.isNotEmpty) {
_mangaList.addAll(data.list);
}
@ -417,7 +433,8 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
return const ProgressCenter();
}
Widget buildProgressIndicator() {
return !(data!.list.isNotEmpty && (_hasNextPage))
return !(data!.list.isNotEmpty &&
(data.hasNextPage || _hasNextPage))
? Container()
: _isLoading
? const Center(
@ -429,45 +446,43 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
),
),
)
: context.isTablet ||
context.isMobile &&
displayType == DisplayType.list
? Padding(
padding: const EdgeInsets.all(4),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(5))),
onPressed: () {
if (!_getManga!.isLoading) {
if (mounted) {
setState(() {
_isLoading = true;
});
}
_loadMore().then((value) {
if (mounted && value != null) {
setState(() {
_mangaList.addAll(value.list);
_isLoading = false;
});
}
: Padding(
padding: const EdgeInsets.all(4),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5))),
onPressed: () {
if (!_getManga!.isLoading) {
if (mounted) {
setState(() {
_isLoading = true;
});
}
_loadMore().then((value) {
if (mounted && value != null) {
setState(() {
_mangaList.addAll(value.list);
_isLoading = false;
});
}
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(l10n.load_more),
const SizedBox(
height: 10,
),
const Icon(Icons.arrow_forward_outlined),
],
)),
)
: Container();
});
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
l10n.load_more,
style: const TextStyle(
overflow: TextOverflow.ellipsis),
maxLines: 2,
),
const Icon(Icons.arrow_forward_outlined),
],
)),
);
}
if (data!.list.isEmpty) {
@ -497,8 +512,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
}
});
_length =
widget.source.isFullData! ? _fullDataLength : _mangaList.length;
_length = source.isFullData! ? _fullDataLength : _mangaList.length;
_length =
(_mangaList.length < _length ? _mangaList.length : _length);
final isComfortableGrid =
@ -517,27 +531,34 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
return buildProgressIndicator();
}
return MangaHomeImageCardListTile(
isManga: widget.source.isManga ?? true,
isManga: source.isManga ?? true,
manga: _mangaList[index],
source: widget.source);
source: source);
})
: GridViewWidget(
controller: _scrollController,
itemCount: _length + 1,
childAspectRatio:
isComfortableGrid ? 0.642 : 0.69,
itemBuilder: (context, index) {
if (index == _length) {
return buildProgressIndicator();
}
return MangaHomeImageCard(
isManga: widget.source.isManga ?? true,
manga: _mangaList[index],
source: widget.source,
isComfortableGrid: isComfortableGrid,
);
},
)),
: Consumer(builder: (context, ref, child) {
final gridSize = ref.watch(
libraryGridSizeStateProvider(
isManga: source.isManga!));
return GridViewWidget(
gridSize: gridSize,
controller: _scrollController,
itemCount: _length + 1,
childAspectRatio:
isComfortableGrid ? 0.642 : 0.69,
itemBuilder: (context, index) {
if (index == _length) {
return buildProgressIndicator();
}
return MangaHomeImageCard(
isManga: source.isManga ?? true,
manga: _mangaList[index],
source: source,
isComfortableGrid: isComfortableGrid,
);
},
);
})),
],
),
);
@ -559,7 +580,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
(_isSearch && _query.isNotEmpty) ||
_isFiltering) {
ref.invalidate(searchProvider(
source: widget.source,
source: source,
query: _query,
page: 1,
filterList: filters));
@ -567,12 +588,12 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
!_isSearch &&
_query.isEmpty) {
ref.invalidate(getLatestUpdatesProvider(
source: widget.source, page: 1));
source: source, page: 1));
} else if (_selectedIndex == 0 &&
!_isSearch &&
_query.isEmpty) {
ref.invalidate(getPopularProvider(
source: widget.source,
source: source,
page: 1,
));
}
@ -588,14 +609,13 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
children: [
IconButton(
onPressed: () async {
final baseUrl = ref.watch(
sourceBaseUrlProvider(source: widget.source));
final baseUrl = ref
.watch(sourceBaseUrlProvider(source: source));
Map<String, dynamic> data = {
'url': baseUrl,
'sourceId': widget.source.id.toString(),
'sourceId': source.id.toString(),
'title': '',
"hasCloudFlare":
widget.source.hasCloudflare ?? false
"hasCloudFlare": source.hasCloudflare ?? false
};
if (Platform.isLinux) {
final url = Uri.parse(baseUrl);

View file

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:mangayomi/eval/model/filter.dart';
import 'package:mangayomi/eval/dart/model/filter.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
class FilterWidget extends StatelessWidget {
@ -171,6 +171,9 @@ class _SeachFormTextFieldWidgetState extends State<SeachFormTextFieldWidget> {
late final _controller = TextEditingController(text: widget.text);
@override
Widget build(BuildContext context) {
if (widget.text.isEmpty) {
_controller.clear();
}
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(

View file

@ -7,6 +7,7 @@ import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provi
import 'package:mangayomi/modules/manga/reader/reader_view.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/modules/widgets/custom_extended_image_provider.dart';
import 'package:mangayomi/utils/headers.dart';
import 'package:mangayomi/utils/reg_exp_matcher.dart';
@ -43,7 +44,7 @@ class ImageViewCenter extends ConsumerWidget {
? ExtendedMemoryImageProvider(archiveImage)
: ExtendedFileImageProvider(
File('${datas.path!.path}${padIndex(datas.index! + 1)}.jpg'))
: ExtendedNetworkImageProvider(datas.url!.trim().trimLeft().trimRight(),
: CustomExtendedNetworkImageProvider(datas.url!.trim().trimLeft().trimRight(),
cache: true,
cacheMaxAge: const Duration(days: 7),
headers: ref.watch(headersProvider(

View file

@ -7,6 +7,7 @@ import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provi
import 'package:mangayomi/modules/manga/reader/reader_view.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/modules/widgets/custom_extended_image_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/utils/headers.dart';
@ -42,7 +43,7 @@ class ImageViewVertical extends ConsumerWidget {
? ExtendedMemoryImageProvider(archiveImage)
: ExtendedFileImageProvider(
File('${datas.path!.path}${padIndex(datas.index! + 1)}.jpg'))
: ExtendedNetworkImageProvider(datas.url!.trim().trimLeft().trimRight(),
: CustomExtendedNetworkImageProvider(datas.url!.trim().trimLeft().trimRight(),
cache: true,
cacheMaxAge: const Duration(days: 7),
headers: ref.watch(headersProvider(

View file

@ -8,9 +8,8 @@ import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/eval/dart/model/m_bridge.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/messages/generated.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
@ -984,8 +983,6 @@ class _MangaChapterPageGalleryState
}
void _initCurrentIndex() async {
await finalizeRust();
await initializeRust();
final readerMode = _readerController.getReaderMode();
_uChapDataPreload.addAll(_chapterUrlModel.uChapDataPreload);
_readerController.setMangaHistoryUpdate();

View file

@ -3,9 +3,9 @@ import 'dart:developer';
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:mangayomi/modules/browse/extension/providers/fetch_manga_sources.dart';
import 'package:mangayomi/services/fetch_manga_sources.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/services/http/interceptor.dart';
import 'package:mangayomi/services/http/m_client.dart';
import 'package:mangayomi/utils/extensions/string_extensions.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -80,7 +80,7 @@ Future<void> _launchInBrowser(Uri url) async {
}
Future<(String, String, String)> _checkUpdate() async {
final http = MInterceptor.init();
final http = MClient.init();
try {
final res = await http.get(Uri.parse(
"https://api.github.com/repos/kodjodevf/Mangayomi/releases?page=1&per_page=10"));

View file

@ -2,7 +2,7 @@ import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/eval/dart/model/m_bridge.dart';
import 'package:mangayomi/modules/manga/detail/widgets/chapter_filter_list_tile_widget.dart';
import 'package:mangayomi/modules/more/backup_and_restore/providers/auto_backup.dart';
import 'package:mangayomi/modules/more/backup_and_restore/providers/backup.dart';

View file

@ -4,7 +4,7 @@ import 'package:archive/archive_io.dart';
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/material.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/source_preference.dart';
import 'package:mangayomi/eval/dart/model/source_preference.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/category.dart';
import 'package:mangayomi/models/chapter.dart';

View file

@ -2,8 +2,8 @@ import 'dart:convert';
import 'package:archive/archive_io.dart';
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/material.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/eval/model/source_preference.dart';
import 'package:mangayomi/eval/dart/model/m_bridge.dart';
import 'package:mangayomi/eval/dart/model/source_preference.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/category.dart';
import 'package:mangayomi/models/chapter.dart';

View file

@ -1,4 +1,4 @@
import 'package:background_downloader/background_downloader.dart';
import 'package:mangayomi/services/background_downloader/background_downloader.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:grouped_list/grouped_list.dart';

View file

@ -6,7 +6,7 @@ part of 'app_font_family.dart';
// RiverpodGenerator
// **************************************************************************
String _$appFontFamilyHash() => r'500f9cd0c0b0dd27d7026c4aa029c33fed337430';
String _$appFontFamilyHash() => r'7f115012111256848d806e47382db1f8abcff5ec';
/// See also [AppFontFamily].
@ProviderFor(AppFontFamily)

View file

@ -6,7 +6,7 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_windows_webview/flutter_windows_webview.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/services/http/interceptor.dart';
import 'package:mangayomi/services/http/m_client.dart';
import 'package:mangayomi/utils/global_style.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:share_plus/share_plus.dart';
@ -57,7 +57,7 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
..launch(widget.url)
..addOnWebMessageReceivedCallback((s) {
if (s.substring(0, 2) == "UA") {
MInterceptor.setCookie(_url, s.replaceFirst("UA", ""));
MClient.setCookie(_url, s.replaceFirst("UA", ""));
}
})
..addScriptToExecuteOnDocumentCreated(
@ -68,7 +68,7 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
for (var c in cookieList) {
final cookie =
c.entries.map((e) => "${e.key}=${e.value}").join(";");
await MInterceptor.setCookie(_url, "", cookie: cookie);
await MClient.setCookie(_url, "", cookie: cookie);
}
}
if (mounted) {
@ -82,7 +82,7 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
widget.url,
WebviewOptions(messageReceiver: (s) {
if (s.substring(0, 2) == "UA") {
MInterceptor.setCookie(_url, s.replaceFirst("UA", ""));
MClient.setCookie(_url, s.replaceFirst("UA", ""));
}
}, onTitleChange: (_) {
_windowsWebview.runScript(
@ -90,7 +90,7 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
_windowsWebview.getCookies(widget.url).then((cookies) {
final cookie =
cookies.entries.map((e) => "${e.key}=${e.value}").join("; ");
MInterceptor.setCookie(_url, "", cookie: cookie);
MClient.setCookie(_url, "", cookie: cookie);
});
}));
}
@ -264,7 +264,7 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
final ua = await controller.evaluateJavascript(
source: "navigator.userAgent") ??
"";
await MInterceptor.setCookie(url.toString(), ua);
await MClient.setCookie(url.toString(), ua);
final canGoback = await controller.canGoBack();
final canGoForward = await controller.canGoForward();
final title = await controller.getTitle();

View file

@ -46,12 +46,9 @@ class CoverViewWidget extends StatelessWidget {
children: children,
)
: Ink.image(
height: 200,
fit: BoxFit.cover,
image: image!,
child: Stack(
children: children,
),
child: Stack(children: children),
),
)),
),

View file

@ -0,0 +1,364 @@
// ignore_for_file: non_nullable_equals_parameter, depend_on_referenced_packages, implementation_imports
import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui show Codec;
import 'package:extended_image_library/src/extended_image_provider.dart';
import 'package:extended_image_library/src/platform.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:http_client_helper/http_client_helper.dart';
import 'package:mangayomi/services/http/m_client.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:extended_image_library/src/network/extended_network_image_provider.dart'
as image_provider;
class CustomExtendedNetworkImageProvider
extends ImageProvider<image_provider.ExtendedNetworkImageProvider>
with ExtendedImageProvider<image_provider.ExtendedNetworkImageProvider>
implements image_provider.ExtendedNetworkImageProvider {
/// Creates an object that fetches the image at the given URL.
///
/// The arguments must not be null.
CustomExtendedNetworkImageProvider(
this.url, {
this.scale = 1.0,
this.headers,
this.cache = true,
this.retries = 3,
this.timeLimit,
this.timeRetry = const Duration(milliseconds: 100),
this.cacheKey,
this.printError = true,
this.cacheRawData = false,
this.cancelToken,
this.imageCacheName,
this.cacheMaxAge = const Duration(days: 30),
});
/// The name of [ImageCache], you can define custom [ImageCache] to store this provider.
@override
final String? imageCacheName;
/// Whether cache raw data if you need to get raw data directly.
/// For example, we need raw image data to edit,
/// but [ui.Image.toByteData()] is very slow. So we cache the image
/// data here.
@override
final bool cacheRawData;
/// The time limit to request image
@override
final Duration? timeLimit;
/// The time to retry to request
@override
final int retries;
/// The time duration to retry to request
@override
final Duration timeRetry;
/// Whether cache image to local
@override
final bool cache;
/// The URL from which the image will be fetched.
@override
final String url;
/// The scale to place in the [ImageInfo] object of the image.
@override
final double scale;
/// The HTTP headers that will be used with [HttpClient.get] to fetch image from network.
@override
final Map<String, String>? headers;
/// The token to cancel network request
@override
final CancellationToken? cancelToken;
/// Custom cache key
@override
final String? cacheKey;
/// print error
@override
final bool printError;
/// The max duration to cahce image.
/// After this time the cache is expired and the image is reloaded.
@override
final Duration? cacheMaxAge;
@override
ImageStreamCompleter loadImage(
image_provider.ExtendedNetworkImageProvider key,
ImageDecoderCallback decode,
) {
// Ownership of this controller is handed off to [_loadAsync]; it is that
// method's responsibility to close the controller's stream when the image
// has been loaded or an error is thrown.
final StreamController<ImageChunkEvent> chunkEvents =
StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter(
codec: _loadAsync(
key as CustomExtendedNetworkImageProvider,
chunkEvents,
decode,
),
scale: key.scale,
chunkEvents: chunkEvents.stream,
debugLabel: key.url,
informationCollector: () {
return <DiagnosticsNode>[
DiagnosticsProperty<ImageProvider>('Image provider', this),
DiagnosticsProperty<image_provider.ExtendedNetworkImageProvider>(
'Image key', key),
];
},
);
}
@override
Future<CustomExtendedNetworkImageProvider> obtainKey(
ImageConfiguration configuration) {
return SynchronousFuture<CustomExtendedNetworkImageProvider>(this);
}
Future<ui.Codec> _loadAsync(
CustomExtendedNetworkImageProvider key,
StreamController<ImageChunkEvent> chunkEvents,
ImageDecoderCallback decode,
) async {
assert(key == this);
final String md5Key = cacheKey ?? keyToMd5(key.url);
ui.Codec? result;
if (cache) {
try {
final Uint8List? data = await _loadCache(
key,
chunkEvents,
md5Key,
);
if (data != null) {
result = await instantiateImageCodec(data, decode);
}
} catch (e) {
if (kDebugMode) {
print(e);
}
}
}
if (result == null) {
try {
final Uint8List? data = await _loadNetwork(
key,
chunkEvents,
);
if (data != null) {
result = await instantiateImageCodec(data, decode);
}
} catch (e) {
if (kDebugMode) {
print(e);
}
}
}
//Failed to load
if (result == null) {
//result = await ui.instantiateImageCodec(kTransparentImage);
return Future<ui.Codec>.error(StateError('Failed to load $url.'));
}
return result;
}
/// Get the image from cache folder.
Future<Uint8List?> _loadCache(
CustomExtendedNetworkImageProvider key,
StreamController<ImageChunkEvent>? chunkEvents,
String md5Key,
) async {
final Directory cacheImagesDirectory = Directory(
join((await getTemporaryDirectory()).path, cacheImageFolderName));
Uint8List? data;
// exist, try to find cache image file
if (cacheImagesDirectory.existsSync()) {
final File cacheFlie = File(join(cacheImagesDirectory.path, md5Key));
if (cacheFlie.existsSync()) {
if (key.cacheMaxAge != null) {
final DateTime now = DateTime.now();
final FileStat fs = cacheFlie.statSync();
if (now.subtract(key.cacheMaxAge!).isAfter(fs.changed)) {
cacheFlie.deleteSync(recursive: true);
} else {
data = await cacheFlie.readAsBytes();
}
} else {
data = await cacheFlie.readAsBytes();
}
}
}
// create folder
else {
await cacheImagesDirectory.create();
}
// load from network
if (data == null) {
data = await _loadNetwork(
key,
chunkEvents,
);
if (data != null) {
// cache image file
await File(join(cacheImagesDirectory.path, md5Key)).writeAsBytes(data);
}
}
return data;
}
/// Get the image from network.
Future<Uint8List?> _loadNetwork(
CustomExtendedNetworkImageProvider key,
StreamController<ImageChunkEvent>? chunkEvents,
) async {
try {
final Uri resolved = Uri.base.resolve(key.url);
final StreamedResponse? response = await _tryGetResponse(resolved);
List<int> bytes = [];
final int total = response!.contentLength ?? 0;
if (response.statusCode == HttpStatus.ok) {
int received = 0;
response.stream.asBroadcastStream();
await for (var chunk in response.stream) {
bytes.addAll(chunk);
try {
received += chunk.length;
if (chunkEvents != null) {}
chunkEvents!.add(ImageChunkEvent(
cumulativeBytesLoaded: received, expectedTotalBytes: total));
} catch (e) {
if (kDebugMode) {
print(e);
}
}
}
} else {
return null;
}
if (bytes.isEmpty) {
return Future<Uint8List>.error(
StateError('NetworkImage is an empty file: $resolved'));
}
return Uint8List.fromList(bytes);
} on OperationCanceledError catch (_) {
if (kDebugMode) {
print('User cancel request $url.');
}
return Future<Uint8List>.error(StateError('User cancel request $url.'));
} catch (e) {
if (kDebugMode) {
print(e);
}
} finally {
await chunkEvents?.close();
}
return null;
}
Future<StreamedResponse> _getResponse(Uri resolved) async {
var request = Request('GET', resolved);
request.headers.addAll(headers ?? {});
StreamedResponse response = await MClient.init().send(request);
return response;
}
// Http get with cancel, delay try again
Future<StreamedResponse?> _tryGetResponse(
Uri resolved,
) async {
cancelToken?.throwIfCancellationRequested();
return await RetryHelper.tryRun<StreamedResponse>(
() {
return CancellationTokenSource.register(
cancelToken,
_getResponse(resolved),
);
},
cancelToken: cancelToken,
timeRetry: timeRetry,
retries: retries,
);
}
@override
bool operator ==(dynamic other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is CustomExtendedNetworkImageProvider &&
url == other.url &&
scale == other.scale &&
cacheRawData == other.cacheRawData &&
timeLimit == other.timeLimit &&
cancelToken == other.cancelToken &&
timeRetry == other.timeRetry &&
cache == other.cache &&
cacheKey == other.cacheKey &&
//headers == other.headers &&
retries == other.retries &&
imageCacheName == other.imageCacheName &&
cacheMaxAge == other.cacheMaxAge;
}
@override
int get hashCode => Object.hash(
url,
scale,
cacheRawData,
timeLimit,
cancelToken,
timeRetry,
cache,
cacheKey,
//headers,
retries,
imageCacheName,
cacheMaxAge,
);
@override
String toString() => '$runtimeType("$url", scale: $scale)';
@override
/// Get network image data from cached
Future<Uint8List?> getNetworkImageData({
StreamController<ImageChunkEvent>? chunkEvents,
}) async {
final String uId = cacheKey ?? keyToMd5(url);
if (cache) {
return await _loadCache(
this,
chunkEvents,
uId,
);
}
return await _loadNetwork(
this,
chunkEvents,
);
}
}

View file

@ -6,27 +6,32 @@ class GridViewWidget extends StatelessWidget {
final bool reverse;
final double? childAspectRatio;
final Widget? Function(BuildContext, int) itemBuilder;
final int? gridSize;
const GridViewWidget(
{super.key,
this.controller,
required this.itemCount,
required this.itemBuilder,
this.reverse = false,
this.childAspectRatio = 0.69});
this.childAspectRatio = 0.69,
this.gridSize});
@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: GridView.builder(
padding: const EdgeInsets.only(top: 13),
controller: controller,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
childAspectRatio: childAspectRatio!,
maxCrossAxisExtent: 220,
),
itemCount: itemCount,
itemBuilder: itemBuilder),
);
color: Theme.of(context).scaffoldBackgroundColor,
child: GridView.builder(
padding: const EdgeInsets.only(top: 13),
controller: controller,
gridDelegate: (gridSize == null || gridSize == 0)
? SliverGridDelegateWithMaxCrossAxisExtent(
childAspectRatio: childAspectRatio!,
maxCrossAxisExtent: 220,
)
: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: gridSize!,
childAspectRatio: childAspectRatio!),
itemCount: itemCount,
itemBuilder: itemBuilder));
}
}

Some files were not shown because too many files have changed in this diff Show more