Added create extension page

This commit is contained in:
kodjomoustapha 2024-03-19 17:32:14 +01:00
parent 5b8245e85c
commit 6f7a179f02
23 changed files with 1201 additions and 147 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -14,4 +14,10 @@ class MChapter {
dateUpload: json['dateUpload'],
scanlator: json['scanlator']);
}
Map<String, dynamic> toJson() => {
'name': name,
'url': url,
'dateUpload': dateUpload,
'scanlator': scanlator
};
}

View file

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

View file

@ -12,4 +12,9 @@ class MPages {
: [],
hasNextPage: json['hasNextPage']);
}
Map<String, dynamic> toJson() => {
'list': list.map((v) => v.toJson()).toList(),
'hasNextPage': hasNextPage,
};
}

View file

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

View file

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

View file

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

View file

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

View file

@ -74,29 +74,41 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
},
controller: _textEditingController,
)
: _tabBarController.index != 4
? IconButton(
splashRadius: 20,
onPressed: () {
if (_tabBarController.index != 1 &&
_tabBarController.index != 0) {
setState(() {
_isSearch = true;
});
} else {
context.push('/globalSearch',
extra: _tabBarController.index == 0
? true
: false);
}
},
icon: Icon(
_tabBarController.index == 0 ||
_tabBarController.index == 1
? Icons.travel_explore_rounded
: Icons.search_rounded,
color: Theme.of(context).hintColor))
: Container(),
: Row(
children: [
if (_tabBarController.index == 2 ||
_tabBarController.index == 3)
IconButton(
onPressed: () {
context.push('/createExtension');
},
icon: Icon(Icons.add_outlined,
color: Theme.of(context).hintColor)),
_tabBarController.index != 4
? IconButton(
splashRadius: 20,
onPressed: () {
if (_tabBarController.index != 1 &&
_tabBarController.index != 0) {
setState(() {
_isSearch = true;
});
} else {
context.push('/globalSearch',
extra: _tabBarController.index == 0
? true
: false);
}
},
icon: Icon(
_tabBarController.index == 0 ||
_tabBarController.index == 1
? Icons.travel_explore_rounded
: Icons.search_rounded,
color: Theme.of(context).hintColor))
: Container(),
],
),
IconButton(
splashRadius: 20,
onPressed: () {

View file

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

View file

@ -1,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(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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