Added create extension page
This commit is contained in:
parent
5b8245e85c
commit
6f7a179f02
23 changed files with 1201 additions and 147 deletions
|
|
@ -15,26 +15,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 +113,12 @@ class $MChapter implements MChapter, $Instance {
|
|||
|
||||
@override
|
||||
set scanlator(String? scanlator) {}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {
|
||||
'name': name,
|
||||
'url': url,
|
||||
'dateUpload': dateUpload,
|
||||
'scanlator': scanlator
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ 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 +191,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()
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,4 +84,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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ 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/utils/log/log.dart';
|
||||
|
||||
class $MProvider extends MProvider with $Bridge<MProvider> {
|
||||
static $MProvider $construct(
|
||||
|
|
@ -737,6 +738,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,6 +760,10 @@ 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) {
|
||||
final runtime = getJavascriptRuntime();
|
||||
return $Future.wrap(runtime
|
||||
|
|
|
|||
|
|
@ -14,4 +14,10 @@ class MChapter {
|
|||
dateUpload: json['dateUpload'],
|
||||
scanlator: json['scanlator']);
|
||||
}
|
||||
Map<String, dynamic> toJson() => {
|
||||
'name': name,
|
||||
'url': url,
|
||||
'dateUpload': dateUpload,
|
||||
'scanlator': scanlator
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
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;
|
||||
|
|
@ -47,4 +48,15 @@ class MManga {
|
|||
.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()
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,4 +12,9 @@ class MPages {
|
|||
: [],
|
||||
hasNextPage: json['hasNextPage']);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'list': list.map((v) => v.toJson()).toList(),
|
||||
'hasNextPage': hasNextPage,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,10 +199,9 @@ class Document {
|
|||
return this.getElement('parent');
|
||||
}
|
||||
getString(type) {
|
||||
return JSON.parse(sendMessage(
|
||||
return sendMessage(
|
||||
"get_doc_string",
|
||||
JSON.stringify([this.html, type]))
|
||||
);
|
||||
JSON.stringify([this.html, type]));
|
||||
}
|
||||
get text() {
|
||||
return this.getString('text');
|
||||
|
|
|
|||
|
|
@ -19,26 +19,23 @@ class JsPreferences {
|
|||
});
|
||||
|
||||
runtime.evaluate('''
|
||||
async function getPreferenceValue(key) {
|
||||
const result = await sendMessage(
|
||||
function getPreferenceValue(key) {
|
||||
return = sendMessage(
|
||||
"getPreferenceValue",
|
||||
JSON.stringify([key])
|
||||
);
|
||||
return result;
|
||||
}
|
||||
async function getPrefStringValue(key,defaultValue) {
|
||||
const result = await sendMessage(
|
||||
function getPrefStringValue(key,defaultValue) {
|
||||
return sendMessage(
|
||||
"getPrefStringValue",
|
||||
JSON.stringify([key,defaultValue])
|
||||
);
|
||||
return result;
|
||||
}
|
||||
async function setPrefStringValue(key,defaultValue) {
|
||||
const result = await sendMessage(
|
||||
function setPrefStringValue(key,defaultValue) {
|
||||
return sendMessage(
|
||||
"setPrefStringValue",
|
||||
JSON.stringify([key,defaultValue])
|
||||
);
|
||||
return result;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,83 +3,84 @@ 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('cryptoHandler', (dynamic args) async {
|
||||
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) async {
|
||||
runtime.onMessage('encryptAESCryptoJS', (dynamic args) {
|
||||
return MBridge.encryptAESCryptoJS(args[0], args[1]);
|
||||
});
|
||||
runtime.onMessage('decryptAESCryptoJS', (dynamic args) async {
|
||||
runtime.onMessage('decryptAESCryptoJS', (dynamic args) {
|
||||
return MBridge.decryptAESCryptoJS(args[0], args[1]);
|
||||
});
|
||||
runtime.onMessage('deobfuscateJsPassword', (dynamic args) async {
|
||||
runtime.onMessage('deobfuscateJsPassword', (dynamic args) {
|
||||
return MBridge.deobfuscateJsPassword(args[0]);
|
||||
});
|
||||
runtime.onMessage('unpackJsAndCombine', (dynamic args) async {
|
||||
runtime.onMessage('unpackJsAndCombine', (dynamic args) {
|
||||
return JsUnpacker.unpackAndCombine(args[0]) ?? "";
|
||||
});
|
||||
runtime.onMessage('unpackJs', (dynamic args) async {
|
||||
runtime.onMessage('unpackJs', (dynamic args) {
|
||||
return JSPacker(args[0]).unpack() ?? "";
|
||||
});
|
||||
runtime.onMessage('parseDates', (dynamic args) async {
|
||||
runtime.onMessage('parseDates', (dynamic args) {
|
||||
return jsonEncode(MBridge.parseDates(args[0], args[1], args[2]));
|
||||
});
|
||||
|
||||
runtime.evaluate('''
|
||||
async function cryptoHandler(text, iv, secretKeyString, encrypt) {
|
||||
const result = await sendMessage(
|
||||
console.log = function (message) {
|
||||
sendMessage("log", JSON.stringify([message.toString()]));
|
||||
};
|
||||
function cryptoHandler(text, iv, secretKeyString, encrypt) {
|
||||
return sendMessage(
|
||||
"cryptoHandler",
|
||||
JSON.stringify([text, iv, secretKeyString, encrypt])
|
||||
);
|
||||
return JSON.parse(result);
|
||||
}
|
||||
async function encryptAESCryptoJS(plainText, passphrase) {
|
||||
const result = await sendMessage(
|
||||
function encryptAESCryptoJS(plainText, passphrase) {
|
||||
return sendMessage(
|
||||
"encryptAESCryptoJS",
|
||||
JSON.stringify([plainText, passphrase])
|
||||
);
|
||||
return JSON.parse(result);
|
||||
}
|
||||
async function decryptAESCryptoJS(encrypted, passphrase) {
|
||||
const result = await sendMessage(
|
||||
function decryptAESCryptoJS(encrypted, passphrase) {
|
||||
return sendMessage(
|
||||
"decryptAESCryptoJS",
|
||||
JSON.stringify([encrypted, passphrase])
|
||||
);
|
||||
return JSON.parse(result);
|
||||
}
|
||||
async function deobfuscateJsPassword(inputString) {
|
||||
const result = await sendMessage(
|
||||
function deobfuscateJsPassword(inputString) {
|
||||
return sendMessage(
|
||||
"deobfuscateJsPassword",
|
||||
JSON.stringify([inputString])
|
||||
);
|
||||
return JSON.parse(result);
|
||||
}
|
||||
async function unpackJsAndCombine(scriptBlock) {
|
||||
const result = await sendMessage(
|
||||
function unpackJsAndCombine(scriptBlock) {
|
||||
return sendMessage(
|
||||
"unpackJsAndCombine",
|
||||
JSON.stringify([scriptBlock])
|
||||
);
|
||||
return JSON.parse(result);
|
||||
}
|
||||
async function unpackJs(packedJS) {
|
||||
const result = await sendMessage(
|
||||
function unpackJs(packedJS) {
|
||||
return sendMessage(
|
||||
"unpackJs",
|
||||
JSON.stringify([packedJS])
|
||||
);
|
||||
return JSON.parse(result);
|
||||
}
|
||||
async function parseDates(value, dateFormat, dateFormatLocale) {
|
||||
const result = await sendMessage(
|
||||
function parseDates(value, dateFormat, dateFormatLocale) {
|
||||
return sendMessage(
|
||||
"parseDates",
|
||||
JSON.stringify([value, dateFormat, dateFormatLocale])
|
||||
);
|
||||
return JSON.parse(result);
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ class Video {
|
|||
'quality': quality,
|
||||
'originalUrl': originalUrl,
|
||||
'headers': headers,
|
||||
'subtitles': subtitles?.map((e) => toJson()),
|
||||
'audios': audios?.map((e) => toJson()),
|
||||
'subtitles': subtitles?.map((e) => e.toJson()).toList(),
|
||||
'audios': audios?.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.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';
|
||||
|
|
@ -33,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: [
|
||||
|
|
@ -128,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(
|
||||
|
|
|
|||
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");
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
|
@ -76,27 +76,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)
|
||||
|
|
@ -125,18 +126,16 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
|
||||
@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);
|
||||
|
|
@ -148,7 +147,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
|
||||
|
|
@ -264,10 +263,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) {
|
||||
|
|
@ -287,7 +286,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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
],
|
||||
|
|
@ -333,7 +338,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
onPressed: () {
|
||||
setState(() {
|
||||
filters = getFilterList(
|
||||
source: widget.source);
|
||||
source: source);
|
||||
});
|
||||
},
|
||||
child: Text(l10n.reset),
|
||||
|
|
@ -382,7 +387,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
}
|
||||
|
||||
_getManga = ref.refresh(searchProvider(
|
||||
source: widget.source,
|
||||
source: source,
|
||||
query: _query,
|
||||
page: 1,
|
||||
filterList: filters));
|
||||
|
|
@ -501,8 +506,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 =
|
||||
|
|
@ -521,15 +525,15 @@ 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);
|
||||
})
|
||||
: Consumer(builder: (context, ref, child) {
|
||||
final gridSize = ref.watch(
|
||||
libraryGridSizeStateProvider(
|
||||
isManga: widget.source.isManga!));
|
||||
final height = ref.watch(widget.source.isManga!
|
||||
isManga: source.isManga!));
|
||||
final height = ref.watch(source.isManga!
|
||||
? mangaCardheightStateProvider
|
||||
: animeCardheightStateProvider);
|
||||
return GridViewWidget(
|
||||
|
|
@ -556,7 +560,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
if (height.ceil() !=
|
||||
newHeight.ceil()) {
|
||||
ref
|
||||
.read((widget.source.isManga!
|
||||
.read((source.isManga!
|
||||
? mangaCardheightStateProvider
|
||||
: animeCardheightStateProvider)
|
||||
.notifier)
|
||||
|
|
@ -565,9 +569,9 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
}
|
||||
},
|
||||
child: MangaHomeImageCard(
|
||||
isManga: widget.source.isManga ?? true,
|
||||
isManga: source.isManga ?? true,
|
||||
manga: _mangaList[index],
|
||||
source: widget.source,
|
||||
source: source,
|
||||
isComfortableGrid: isComfortableGrid,
|
||||
),
|
||||
),
|
||||
|
|
@ -596,7 +600,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
(_isSearch && _query.isNotEmpty) ||
|
||||
_isFiltering) {
|
||||
ref.invalidate(searchProvider(
|
||||
source: widget.source,
|
||||
source: source,
|
||||
query: _query,
|
||||
page: 1,
|
||||
filterList: filters));
|
||||
|
|
@ -604,12 +608,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,
|
||||
));
|
||||
}
|
||||
|
|
@ -625,14 +629,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);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import 'package:mangayomi/models/chapter.dart';
|
|||
import 'package:mangayomi/models/source.dart';
|
||||
import 'package:mangayomi/models/track_preference.dart';
|
||||
import 'package:mangayomi/modules/anime/anime_player_view.dart';
|
||||
import 'package:mangayomi/modules/browse/extension/edit_code.dart';
|
||||
import 'package:mangayomi/modules/browse/extension/extension_detail.dart';
|
||||
import 'package:mangayomi/modules/browse/extension/widgets/create_extension.dart';
|
||||
import 'package:mangayomi/modules/browse/sources/sources_filter_screen.dart';
|
||||
import 'package:mangayomi/modules/more/backup_and_restore/backup_and_restore.dart';
|
||||
import 'package:mangayomi/modules/more/categories/categories_screen.dart';
|
||||
|
|
@ -503,6 +505,34 @@ class RouterNotifier extends ChangeNotifier {
|
|||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: "/codeEditor",
|
||||
name: "codeEditor",
|
||||
builder: (context, state) {
|
||||
final sourceId = state.extra as int?;
|
||||
return CodeEditor(sourceId: sourceId);
|
||||
},
|
||||
pageBuilder: (context, state) {
|
||||
final sourceId = state.extra as int?;
|
||||
return transitionPage(
|
||||
key: state.pageKey,
|
||||
child: CodeEditor(sourceId: sourceId),
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: "/createExtension",
|
||||
name: "createExtension",
|
||||
builder: (context, state) {
|
||||
return const CreateExtension();
|
||||
},
|
||||
pageBuilder: (context, state) {
|
||||
return transitionPage(
|
||||
key: state.pageKey,
|
||||
child: const CreateExtension(),
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import 'package:mangayomi/sources/source_test.dart';
|
|||
|
||||
List<dynamic> getFilterList({required Source source}) {
|
||||
List<dynamic> filterList = [];
|
||||
if (source.sourceCodeLanguage == SourceCodeLanguage.dart) {
|
||||
try {
|
||||
|
||||
try {
|
||||
if (source.sourceCodeLanguage == SourceCodeLanguage.dart) {
|
||||
final bytecode =
|
||||
compilerEval(useTestSourceCode ? testSourceCode : source.sourceCode!);
|
||||
|
||||
|
|
@ -22,11 +23,11 @@ List<dynamic> getFilterList({required Source source}) {
|
|||
.getFilterList()
|
||||
.map((e) => e is $Value ? e.$reified : e)
|
||||
.toList();
|
||||
} catch (_) {
|
||||
return [];
|
||||
} else {
|
||||
filterList = (JsExtensionService(source).getFilterList()).filters;
|
||||
}
|
||||
} else {
|
||||
filterList = (JsExtensionService(source).getFilterList()).filters;
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return filterList;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ import 'dart:io';
|
|||
import 'package:mangayomi/eval/dart/bridge/m_source.dart';
|
||||
import 'package:mangayomi/eval/dart/model/m_provider.dart';
|
||||
import 'package:mangayomi/eval/dart/compiler/compiler.dart';
|
||||
import 'package:mangayomi/eval/javascript/service.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/source.dart';
|
||||
import 'package:mangayomi/models/video.dart';
|
||||
import 'package:mangayomi/eval/dart/runtime/runtime.dart';
|
||||
import 'package:mangayomi/providers/storage_provider.dart';
|
||||
|
|
@ -36,14 +38,18 @@ Future<(List<Video>, bool, String?)> getVideoList(
|
|||
await MTorrentServer().getTorrentPlaylist(episode.url!);
|
||||
return (videos, false, infohash);
|
||||
}
|
||||
List<Video> list = [];
|
||||
if (source.sourceCodeLanguage == SourceCodeLanguage.dart) {
|
||||
final bytecode =
|
||||
compilerEval(useTestSourceCode ? testSourceCode : source.sourceCode!);
|
||||
|
||||
final bytecode =
|
||||
compilerEval(useTestSourceCode ? testSourceCode : source.sourceCode!);
|
||||
final runtime = runtimeEval(bytecode);
|
||||
|
||||
final runtime = runtimeEval(bytecode);
|
||||
|
||||
var res = runtime.executeLib('package:mangayomi/main.dart', 'main',
|
||||
[$MSource.wrap(source.toMSource())]);
|
||||
final dd = (await (res as MProvider).getVideoList(episode.url!));
|
||||
return (dd, false, null);
|
||||
var res = runtime.executeLib('package:mangayomi/main.dart', 'main',
|
||||
[$MSource.wrap(source.toMSource())]);
|
||||
list = (await (res as MProvider).getVideoList(episode.url!));
|
||||
} else {
|
||||
list = await JsExtensionService(source).getVideoList(episode.url!);
|
||||
}
|
||||
return (list, false, null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:cupertino_http/cupertino_http.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:google_api_availability/google_api_availability.dart';
|
||||
import 'package:http_interceptor/http_interceptor.dart';
|
||||
import 'package:mangayomi/eval/dart/model/m_bridge.dart';
|
||||
|
|
@ -12,6 +11,7 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart'
|
|||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:cronet_http/cronet_http.dart';
|
||||
import 'package:http/io_client.dart';
|
||||
import 'package:mangayomi/utils/log/log.dart';
|
||||
|
||||
class MClient {
|
||||
static final flutter_inappwebview.CookieManager _cookieManager =
|
||||
|
|
@ -20,7 +20,7 @@ class MClient {
|
|||
MClient();
|
||||
static Client httpClient() {
|
||||
if (Platform.isAndroid) {
|
||||
if (playStoreAvailability == GooglePlayServicesAvailability.unknown) {
|
||||
if (playStoreAvailability != GooglePlayServicesAvailability.success) {
|
||||
return IOClient(HttpClient());
|
||||
}
|
||||
final engine = CronetEngine.build(
|
||||
|
|
@ -142,11 +142,8 @@ class LoggerInterceptor extends InterceptorContract {
|
|||
Future<BaseRequest> interceptRequest({
|
||||
required BaseRequest request,
|
||||
}) async {
|
||||
if (kDebugMode) {
|
||||
print('----- Request -----');
|
||||
print(request.toString());
|
||||
print(request.headers.toString());
|
||||
}
|
||||
Logger.add(LoggerLevel.info,
|
||||
'----- Request -----\n${request.toString()}\nheader: ${request.headers.toString()}');
|
||||
return request;
|
||||
}
|
||||
|
||||
|
|
@ -156,11 +153,8 @@ class LoggerInterceptor extends InterceptorContract {
|
|||
}) async {
|
||||
final cloudflare = [403, 503].contains(response.statusCode) &&
|
||||
["cloudflare-nginx", "cloudflare"].contains(response.headers["server"]);
|
||||
if (kDebugMode) {
|
||||
print('----- Response -----');
|
||||
print(
|
||||
"${response.request?.method}: ${response.request?.url}, statusCode: ${response.statusCode} ${cloudflare ? "Failed to bypass Cloudflare" : ""}");
|
||||
}
|
||||
Logger.add(LoggerLevel.info,
|
||||
"----- Response -----\n${response.request?.method}: ${response.request?.url}, statusCode: ${response.statusCode} ${cloudflare ? "Failed to bypass Cloudflare" : ""}");
|
||||
if (cloudflare) {
|
||||
botToast("${response.statusCode} Failed to bypass Cloudflare");
|
||||
}
|
||||
|
|
|
|||
29
lib/utils/log/log.dart
Normal file
29
lib/utils/log/log.dart
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
class Logger {
|
||||
static final StreamController<(LoggerLevel, String, DateTime)>
|
||||
_logStreamController =
|
||||
StreamController<(LoggerLevel, String, DateTime)>.broadcast();
|
||||
|
||||
static StreamController<(LoggerLevel, String, DateTime)>
|
||||
get logStreamController => _logStreamController;
|
||||
|
||||
static final List<(LoggerLevel, String, DateTime)> _logs = [];
|
||||
|
||||
static List<(LoggerLevel, String, DateTime)> get logs => _logs;
|
||||
|
||||
static void add(LoggerLevel level, String content) {
|
||||
if (kDebugMode) {
|
||||
print(content);
|
||||
}
|
||||
_logStreamController.add((level, content, DateTime.now()));
|
||||
_logs.add((level, content, DateTime.now()));
|
||||
}
|
||||
|
||||
static void clear() {
|
||||
_logs.clear();
|
||||
}
|
||||
}
|
||||
|
||||
enum LoggerLevel { error, warning, info }
|
||||
88
pubspec.lock
88
pubspec.lock
|
|
@ -57,6 +57,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
autotrie:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: autotrie
|
||||
sha256: "55da6faefb53cfcb0abb2f2ca8636123fb40e35286bb57440d2cf467568188f8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
background_downloader:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -154,6 +162,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -345,6 +361,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.3"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: equatable
|
||||
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
exception_templates:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -454,6 +478,22 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_code_editor:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_code_editor
|
||||
sha256: "56673b62f2d844c1a2b0cf43a4495be0d6123111de5f33887781d2d3b39a77b6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
flutter_highlight:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_highlight
|
||||
sha256: "7b96333867aa07e122e245c033b8ad622e4e3a42a1a2372cbb098a2541d8782c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
flutter_inappwebview:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -631,6 +671,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.2"
|
||||
highlight:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: highlight
|
||||
sha256: "5353a83ffe3e3eca7df0abfb72dcf3fa66cc56b953728e7113ad4ad88497cf21"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
hive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hive
|
||||
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
html:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -783,6 +839,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
json_view:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: json_view
|
||||
sha256: "905c69f9e69d1eab5406b87ab6c10c3706c04c70c6a4959621bd2b43c2d27374"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
lazy_memo:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -815,6 +879,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
linked_scroll_controller:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: linked_scroll_controller
|
||||
sha256: e6020062bcf4ffc907ee7fd090fa971e65d8dfaac3c62baf601a3ced0b37986a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -944,6 +1016,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
mocktail:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mocktail
|
||||
sha256: c4b5007d91ca4f67256e720cb1b6d704e79a510183a12fa551021f652577dce6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
numberpicker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1437,6 +1517,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
tuple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: tuple
|
||||
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@ dependencies:
|
|||
cupertino_http: ^1.3.0
|
||||
http: ^1.2.0
|
||||
google_api_availability: ^5.0.0
|
||||
flutter_code_editor: ^0.3.1
|
||||
flutter_highlight: ^0.7.0
|
||||
highlight: ^0.7.0
|
||||
json_view: ^0.4.2
|
||||
|
||||
|
||||
dependency_overrides:
|
||||
|
|
|
|||
Loading…
Reference in a new issue