import 'package:flutter/material.dart'; import 'package:mangayomi/eval/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 createState() => _CreateExtensionState(); } class _CreateExtensionState extends State { bool _isManga = false; String _name = ""; String _lang = ""; String _baseUrl = ""; String _apiUrl = ""; String _iconUrl = ""; int _sourceTypeIndex = 0; int _languageIndex = 0; final List _sourceTypes = ["single", "multi", "torrent"]; final List _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("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: false) ..sourceCodeLanguage = _sourceCodeLanguage; source = source ..isLocal = true ..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.text, 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 Map get headers => {}; @override Future getPopular(int page) async { // TODO: implement } @override Future getLatestUpdates(int page) async { // TODO: implement } @override Future search(String query, int page, FilterList filterList) async { // TODO: implement } @override Future getDetail(String url) async { // TODO: implement } // For anime episode video list @override Future> getVideoList(String url) async { // TODO: implement } // For manga chapter pages @override Future> getPageList(String url) async{ // TODO: implement } @override List getFilterList() { // TODO: implement } @override List getSourcePreferences() { // TODO: implement } } TestSource main(MSource source) { return TestSource(source:source); }'''; String _jsSample(Source source) => ''' const mangayomiSources = [{ "name": "${source.name}", "lang": "${source.lang}", "baseUrl": "${source.baseUrl}", "apiUrl": "${source.apiUrl}", "iconUrl": "${source.iconUrl}", "typeSource": "${source.typeSource}", "isManga": ${source.isManga}, "version": "${source.version}", "dateFormat": "", "dateFormatLocale": "", "pkgPath": "" }]; class DefaultExtension extends MProvider { getHeaders(url) { throw new Error("getHeaders not implemented"); } async getPopular(page) { throw new Error("getPopular not implemented"); } get supportsLatest() { throw new Error("supportsLatest 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"); } } ''';