Merge pull request #150 from kodjodevf/feat/javascript_extension
Feat/javascript extension support
This commit is contained in:
commit
c7cfd040d9
197 changed files with 13215 additions and 2386 deletions
1315
Cargo.lock
generated
1315
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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();
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
237
lib/eval/dart/model/filter.dart
Normal file
237
lib/eval/dart/model/filter.dart
Normal 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();
|
||||
}
|
||||
|
|
@ -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';
|
||||
23
lib/eval/dart/model/m_chapter.dart
Normal file
23
lib/eval/dart/model/m_chapter.dart
Normal 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
|
||||
};
|
||||
}
|
||||
74
lib/eval/dart/model/m_manga.dart
Normal file
74
lib/eval/dart/model/m_manga.dart
Normal 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()
|
||||
};
|
||||
}
|
||||
20
lib/eval/dart/model/m_pages.dart
Normal file
20
lib/eval/dart/model/m_pages.dart
Normal 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,
|
||||
};
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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());
|
||||
385
lib/eval/javascript/dom_selector.dart
Normal file
385
lib/eval/javascript/dom_selector.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
''');
|
||||
}
|
||||
}
|
||||
172
lib/eval/javascript/extractors.dart
Normal file
172
lib/eval/javascript/extractors.dart
Normal 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());
|
||||
}
|
||||
}
|
||||
134
lib/eval/javascript/http.dart
Normal file
134
lib/eval/javascript/http.dart
Normal 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()));
|
||||
}
|
||||
}
|
||||
44
lib/eval/javascript/preferences.dart
Normal file
44
lib/eval/javascript/preferences.dart
Normal 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])
|
||||
);
|
||||
}
|
||||
}
|
||||
''');
|
||||
}
|
||||
}
|
||||
145
lib/eval/javascript/service.dart
Normal file
145
lib/eval/javascript/service.dart
Normal 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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
108
lib/eval/javascript/utils.dart
Normal file
108
lib/eval/javascript/utils.dart
Normal 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])
|
||||
);
|
||||
}
|
||||
''');
|
||||
}
|
||||
}
|
||||
|
|
@ -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});
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
class MChapter {
|
||||
String? name;
|
||||
|
||||
String? url;
|
||||
|
||||
String? dateUpload;
|
||||
|
||||
String? scanlator;
|
||||
MChapter({this.name, this.url, this.dateUpload, this.scanlator});
|
||||
}
|
||||
|
|
@ -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});
|
||||
}
|
||||
|
|
@ -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});
|
||||
}
|
||||
|
|
@ -317,5 +317,7 @@
|
|||
"text": "النص",
|
||||
"border": "الحدود",
|
||||
"background": "الخلفية",
|
||||
"no_subtite_warning_message": "لا تؤثر لأنه لا توجد مسارات ترجمة في هذا الفيديو"
|
||||
"no_subtite_warning_message": "لا تؤثر لأنه لا توجد مسارات ترجمة في هذا الفيديو",
|
||||
"grid_size": "حجم الشبكة",
|
||||
"n_per_row": "{n} في الصف الواحد"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -317,5 +317,7 @@
|
|||
"text": "Текст",
|
||||
"border": "Граница",
|
||||
"background": "Фон",
|
||||
"no_subtite_warning_message": "Не имеет эффекта, потому что в этом видео нет субтитров"
|
||||
"no_subtite_warning_message": "Не имеет эффекта, потому что в этом видео нет субтитров",
|
||||
"grid_size": "Размер сетки",
|
||||
"n_per_row": "{n} в ряд"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -319,5 +319,7 @@
|
|||
"text": "文本",
|
||||
"border": "边框",
|
||||
"background": "背景",
|
||||
"no_subtite_warning_message": "由于此视频中没有字幕轨道,因此无效。"
|
||||
"no_subtite_warning_message": "由于此视频中没有字幕轨道,因此无效。",
|
||||
"grid_size": "网格大小",
|
||||
"n_per_row": "{n} 每行"
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ part of 'state_provider.dart';
|
|||
// **************************************************************************
|
||||
|
||||
String _$subtitleSettingsStateHash() =>
|
||||
r'4f668c79675772a76d80585db43d041675d0d178';
|
||||
r'4b89ea55392e662651d5aeee4dfce2fcd23ac0e7';
|
||||
|
||||
/// See also [SubtitleSettingsState].
|
||||
@ProviderFor(SubtitleSettingsState)
|
||||
|
|
|
|||
|
|
@ -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: () {
|
||||
|
|
|
|||
461
lib/modules/browse/extension/edit_code.dart
Normal file
461
lib/modules/browse/extension/edit_code.dart
Normal 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))),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
331
lib/modules/browse/extension/widgets/create_extension.dart
Normal file
331
lib/modules/browse/extension/widgets/create_extension.dart
Normal 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");
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
);
|
||||
},
|
||||
)))
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!),
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'download_provider.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$downloadChapterHash() => r'dcb0ade7f80a1ec593667e7311cdafc2d31222cf';
|
||||
String _$downloadChapterHash() => r'a8bbd438963157af07b92601c1741195421a3281';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'app_font_family.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$appFontFamilyHash() => r'500f9cd0c0b0dd27d7026c4aa029c33fed337430';
|
||||
String _$appFontFamilyHash() => r'7f115012111256848d806e47382db1f8abcff5ec';
|
||||
|
||||
/// See also [AppFontFamily].
|
||||
@ProviderFor(AppFontFamily)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
)),
|
||||
),
|
||||
|
|
|
|||
364
lib/modules/widgets/custom_extended_image_provider.dart
Normal file
364
lib/modules/widgets/custom_extended_image_provider.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in a new issue