mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-04-20 15:02:07 +00:00
add anime serervers extractor , fix home page load more ,add hasNextPage check
This commit is contained in:
parent
47734532b3
commit
0d7f4e276f
18 changed files with 282 additions and 71 deletions
|
|
@ -153,6 +153,11 @@ class $MangaModel implements MangaModel, $Instance {
|
|||
[BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
|
||||
),
|
||||
false),
|
||||
BridgeParameter(
|
||||
'hasNextPage',
|
||||
BridgeTypeAnnotation(
|
||||
BridgeTypeRef.type(RuntimeTypes.boolType)),
|
||||
false),
|
||||
]))
|
||||
},
|
||||
// Specify class fields
|
||||
|
|
@ -241,6 +246,11 @@ class $MangaModel implements MangaModel, $Instance {
|
|||
CoreTypes.list, [BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
|
||||
),
|
||||
),
|
||||
'hasNextPage': BridgeFieldDef(
|
||||
BridgeTypeAnnotation(
|
||||
BridgeTypeRef.type(RuntimeTypes.boolType),
|
||||
),
|
||||
),
|
||||
},
|
||||
wrap: true);
|
||||
|
||||
|
|
@ -357,7 +367,8 @@ class $MangaModel implements MangaModel, $Instance {
|
|||
return $List.wrap($value.statusList!.map((e) {
|
||||
return $int(e);
|
||||
}).toList());
|
||||
|
||||
case 'hasNextPage':
|
||||
return $bool($value.hasNextPage!);
|
||||
default:
|
||||
return _superclass.$getProperty(runtime, identifier);
|
||||
}
|
||||
|
|
@ -417,7 +428,8 @@ class $MangaModel implements MangaModel, $Instance {
|
|||
$value.images = value.$reified as List<dynamic>;
|
||||
case 'statusList':
|
||||
$value.statusList = value.$reified as List<dynamic>;
|
||||
|
||||
case 'hasNextPage':
|
||||
$value.hasNextPage = value.$reified;
|
||||
default:
|
||||
_superclass.$setProperty(runtime, identifier, value);
|
||||
}
|
||||
|
|
@ -480,6 +492,10 @@ class $MangaModel implements MangaModel, $Instance {
|
|||
set source(String? source) {
|
||||
// implement source
|
||||
}
|
||||
@override
|
||||
set hasNextPage(bool? hasNextPage) {
|
||||
// implement hasNextPage
|
||||
}
|
||||
|
||||
@override
|
||||
List<dynamic>? get chaptersDateUploads => $value.chaptersDateUploads;
|
||||
|
|
@ -549,6 +565,9 @@ class $MangaModel implements MangaModel, $Instance {
|
|||
@override
|
||||
String? get dateFormatLocale => $value.dateFormatLocale;
|
||||
|
||||
@override
|
||||
bool? get hasNextPage => $value.hasNextPage;
|
||||
|
||||
@override
|
||||
set apiUrl(String? apiUrl) {
|
||||
// implement apiUrl
|
||||
|
|
@ -581,7 +600,7 @@ class $MangaModel implements MangaModel, $Instance {
|
|||
|
||||
@override
|
||||
set query(String? query) {
|
||||
// implement page
|
||||
// implement query
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -599,7 +618,7 @@ class $MangaModel implements MangaModel, $Instance {
|
|||
}
|
||||
@override
|
||||
set statusList(List? images) {
|
||||
// implement statusList
|
||||
// implement images
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ class MangaModel {
|
|||
|
||||
int? sourceId;
|
||||
|
||||
bool? hasNextPage;
|
||||
|
||||
List<dynamic>? names;
|
||||
List<dynamic>? urls;
|
||||
List<dynamic>? chaptersScanlators;
|
||||
|
|
@ -63,7 +65,8 @@ class MangaModel {
|
|||
this.chaptersVolumes,
|
||||
this.chaptersChaps,
|
||||
this.images,
|
||||
this.statusList});
|
||||
this.statusList,
|
||||
this.hasNextPage = true});
|
||||
}
|
||||
|
||||
class VideoModel {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import 'package:mangayomi/main.dart';
|
|||
import 'package:mangayomi/models/source.dart';
|
||||
import 'package:mangayomi/models/video.dart';
|
||||
import 'package:mangayomi/modules/webview/webview.dart';
|
||||
import 'package:mangayomi/services/anime_extractors/streamwish_extractor.dart';
|
||||
import 'package:mangayomi/services/anime_extractors/vidbom_extractor.dart';
|
||||
import 'package:mangayomi/services/anime_extractors/voe_extractor.dart';
|
||||
import 'package:mangayomi/services/anime_extractors/your_upload_extractor.dart';
|
||||
|
|
@ -538,6 +539,11 @@ class MBridge {
|
|||
);
|
||||
}
|
||||
|
||||
static Future<List<Video>> streamWishExtractor(
|
||||
String url, String prefix) async {
|
||||
return await StreamWishExtractor().videosFromUrl(url, prefix);
|
||||
}
|
||||
|
||||
static Future<List<Video>> mp4UploadExtractor(
|
||||
String url, String? headers, String prefix, String suffix) async {
|
||||
Map<String, String> newHeaders = {};
|
||||
|
|
@ -1430,6 +1436,24 @@ class $MBridge extends MBridge with $Bridge {
|
|||
],
|
||||
namedParams: []),
|
||||
isStatic: true),
|
||||
'streamWishExtractor': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.future,
|
||||
[BridgeTypeRef.type(RuntimeTypes.dynamicType)])),
|
||||
params: [
|
||||
BridgeParameter(
|
||||
'url',
|
||||
BridgeTypeAnnotation(
|
||||
BridgeTypeRef.type(RuntimeTypes.stringType)),
|
||||
false),
|
||||
BridgeParameter(
|
||||
'prefix',
|
||||
BridgeTypeAnnotation(
|
||||
BridgeTypeRef.type(RuntimeTypes.stringType)),
|
||||
false),
|
||||
],
|
||||
namedParams: []),
|
||||
isStatic: true),
|
||||
'getHtmlViaWebview': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.future,
|
||||
|
|
@ -1725,6 +1749,12 @@ class $MBridge extends MBridge with $Bridge {
|
|||
.then((value) =>
|
||||
$List.wrap(value.map((e) => _toVideoModel(e)).toList())));
|
||||
|
||||
static $Future $streamWishExtractor(
|
||||
Runtime runtime, $Value? target, List<$Value?> args) =>
|
||||
$Future.wrap(MBridge.streamWishExtractor(args[0]!.$value, args[1]!.$value)
|
||||
.then((value) =>
|
||||
$List.wrap(value.map((e) => _toVideoModel(e)).toList())));
|
||||
|
||||
static $Future $sendVidExtractor(
|
||||
Runtime runtime, $Value? target, List<$Value?> args) =>
|
||||
$Future.wrap(MBridge.sendVidExtractor(
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ Runtime runtimeEval(Uint8List bytecode) {
|
|||
'MBridge.doodExtractor', $MBridge.$doodExtractor);
|
||||
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
|
||||
'MBridge.streamTapeExtractor', $MBridge.$streamTapeExtractor);
|
||||
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
|
||||
'MBridge.streamWishExtractor', $MBridge.$streamWishExtractor);
|
||||
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
|
||||
'MBridge.mp4UploadExtractor', $MBridge.$mp4UploadExtractor);
|
||||
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ class _AnimeStreamPageState extends State<AnimeStreamPage> {
|
|||
await _controller.setDataSource(
|
||||
DataSource(
|
||||
type: DataSourceType.network,
|
||||
source: _video.value!.url,
|
||||
source: _video.value!.originalUrl,
|
||||
httpHeaders: _video.value!.headers),
|
||||
autoplay: true,
|
||||
seekTo: _currentPosition,
|
||||
|
|
|
|||
|
|
@ -199,50 +199,53 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
body: _getManga!.when(
|
||||
data: (data) {
|
||||
Widget buildProgressIndicator() {
|
||||
return _isLoading
|
||||
? const Center(
|
||||
child: SizedBox(
|
||||
height: 100,
|
||||
width: 200,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
)
|
||||
: isTablet(context)
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5))),
|
||||
onPressed: () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
}
|
||||
_loadMore().then((value) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
data.addAll(value);
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
child: const Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text("Load more"),
|
||||
SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Icon(Icons.arrow_forward_outlined),
|
||||
],
|
||||
)),
|
||||
return !(data.isNotEmpty && (data.last!.hasNextPage ?? true))
|
||||
? Container()
|
||||
: _isLoading
|
||||
? const Center(
|
||||
child: SizedBox(
|
||||
height: 100,
|
||||
width: 200,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container();
|
||||
: isTablet(context)
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(5))),
|
||||
onPressed: () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
}
|
||||
_loadMore().then((value) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
data.addAll(value);
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
child: const Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text("Load more"),
|
||||
SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Icon(Icons.arrow_forward_outlined),
|
||||
],
|
||||
)),
|
||||
)
|
||||
: Container();
|
||||
}
|
||||
|
||||
if (data.isEmpty) {
|
||||
|
|
@ -251,19 +254,21 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
_scrollController.addListener(() {
|
||||
if (_scrollController.position.pixels ==
|
||||
_scrollController.position.maxScrollExtent) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
}
|
||||
_loadMore().then((value) {
|
||||
if (data.isNotEmpty && (data.last!.hasNextPage ?? true)) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
data.addAll(value);
|
||||
_isLoading = false;
|
||||
_isLoading = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
_loadMore().then((value) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
data.addAll(value);
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ class DoodExtractor {
|
|||
Future<List<Video>> videosFromUrl(
|
||||
String url, {
|
||||
String? quality,
|
||||
bool redirect = false,
|
||||
bool redirect = true,
|
||||
}) async {
|
||||
final newQuality = quality ?? ('Doodstream${redirect ? ' mirror' : ''}');
|
||||
|
||||
|
|
|
|||
56
lib/services/anime_extractors/mystream_extractor.dart
Normal file
56
lib/services/anime_extractors/mystream_extractor.dart
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import 'dart:async';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mangayomi/models/video.dart';
|
||||
import 'package:mangayomi/utils/extensions.dart';
|
||||
|
||||
class MyStreamExtractor {
|
||||
Future<List<Video>> videosFromUrl(
|
||||
String url, Map<String, String> headers) async {
|
||||
final host = url.substringBefore("/watch");
|
||||
|
||||
final client = http.Client();
|
||||
|
||||
try {
|
||||
final response = await client.get(Uri.parse(url), headers: headers);
|
||||
|
||||
final document = response.body;
|
||||
final streamCode = document
|
||||
.substringAfter("${url.substringAfter("?v=")}\", \"")
|
||||
.substringBefore("\",null,null");
|
||||
final streamUrl = "$host/m3u8/$streamCode/master.txt?s=1&cache=1";
|
||||
|
||||
final cookie = response.headers.entries
|
||||
.firstWhere(
|
||||
(entry) =>
|
||||
entry.key.toLowerCase() == "set-cookie" &&
|
||||
entry.value.startsWith("PHPSESSID", 0),
|
||||
orElse: () => const MapEntry("set-cookie", ""),
|
||||
)
|
||||
.value
|
||||
.split(";")
|
||||
.first;
|
||||
|
||||
final newHeaders = {...headers, "cookie": cookie, "accept": "*/*"};
|
||||
|
||||
final masterPlaylistResponse =
|
||||
await client.get(Uri.parse(streamUrl), headers: newHeaders);
|
||||
final masterPlaylist = masterPlaylistResponse.body;
|
||||
|
||||
const separator = "#EXT-X-STREAM-INF";
|
||||
return masterPlaylist
|
||||
.substringAfter(separator)
|
||||
.split(separator)
|
||||
.map((it) {
|
||||
final resolution =
|
||||
"${it.substringAfter("RESOLUTION=").substringBefore("\n").substringAfter("x").substringBefore(",")}p";
|
||||
final quality = "MyStream: $resolution";
|
||||
final videoUrl = it.substringAfter("\n").substringBefore("\n");
|
||||
return Video(videoUrl, quality, videoUrl, headers: newHeaders);
|
||||
}).toList();
|
||||
} catch (_) {
|
||||
return [];
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ class OkruExtractor {
|
|||
'lowest': '240p',
|
||||
'mobile': '144p',
|
||||
};
|
||||
return qualities[quality] ?? quality;
|
||||
return qualities[quality.toLowerCase()] ?? quality;
|
||||
}
|
||||
|
||||
Future<List<Video>> videosFromUrl(String url,
|
||||
|
|
@ -27,17 +27,19 @@ class OkruExtractor {
|
|||
final videosString = document
|
||||
.querySelector('div[data-options]')
|
||||
?.attributes['data-options']!
|
||||
.substringAfter(
|
||||
'''\\"videos\\":[{\\\\"name\\\\":\\\\"''').substringBefore(']') ??
|
||||
.substringAfter("\\\"videos\\\":[{\\\"name\\\":\\\"")
|
||||
.substringBefore(']') ??
|
||||
'';
|
||||
|
||||
List<Video> videoList = [];
|
||||
List<String> values =
|
||||
videosString.split('''{\\\\"name\\\\":\\\\"''').reversed.toList();
|
||||
videosString.split("{\\\"name\\\":\\\"").reversed.toList();
|
||||
for (var value in values) {
|
||||
final videoUrl = value
|
||||
.substringAfter('''url\\\\":\\\\"''').substringBefore(
|
||||
'''\\"''').replaceAll(r'\\u0026', '&');
|
||||
final quality = value.substringBefore('''\\"''');
|
||||
.substringAfter("url\\\":\\\"")
|
||||
.substringBefore("\\\"")
|
||||
.replaceAll(r'\\\u0026', '&');
|
||||
final quality = value.substringBefore("\\\"");
|
||||
final fixedQuality = fixQualities ? fixQuality(quality) : quality;
|
||||
final videoQuality =
|
||||
'${prefix.isNotEmpty ? '$prefix ' : ''}Okru:$fixedQuality';
|
||||
|
|
|
|||
62
lib/services/anime_extractors/streamwish_extractor.dart
Normal file
62
lib/services/anime_extractors/streamwish_extractor.dart
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import 'package:js_packer/js_packer.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mangayomi/models/video.dart';
|
||||
import 'package:mangayomi/utils/extensions.dart';
|
||||
import 'package:mangayomi/utils/xpath_selector.dart';
|
||||
|
||||
class StreamWishExtractor {
|
||||
final http.Client client = http.Client();
|
||||
final Map<String, String> headers = {};
|
||||
|
||||
Future<List<Video>> videosFromUrl(String url, String prefix) async {
|
||||
final videoList = <Video>[];
|
||||
|
||||
final response = await client.get(Uri.parse(url), headers: headers);
|
||||
|
||||
final jsEval = xpathSelector(response.body)
|
||||
.queryXPath('//script[contains(text(), "m3u8")]/text()')
|
||||
.attrs;
|
||||
if (jsEval.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
String? masterUrl = _evalJs(jsEval.first!)
|
||||
?.substringAfter('source')
|
||||
.substringAfter('file:"')
|
||||
.substringBefore('"');
|
||||
|
||||
if (masterUrl == null) return [];
|
||||
|
||||
final playlistHeaders = Map<String, String>.from(headers)
|
||||
..addAll({
|
||||
'Accept': '*/*',
|
||||
'Host': Uri.parse(masterUrl).host,
|
||||
'Origin': 'https://${Uri.parse(url).host}',
|
||||
'Referer': 'https://${Uri.parse(url).host}/',
|
||||
});
|
||||
|
||||
final masterBase =
|
||||
'${'https://${Uri.parse(masterUrl).host}${Uri.parse(masterUrl).path}'.substringBeforeLast('/')}/';
|
||||
|
||||
final masterPlaylistResponse =
|
||||
await client.get(Uri.parse(masterUrl), headers: playlistHeaders);
|
||||
final masterPlaylist = masterPlaylistResponse.body;
|
||||
|
||||
const separator = '#EXT-X-STREAM-INF:';
|
||||
masterPlaylist.substringAfter(separator).split(separator).forEach((it) {
|
||||
final quality =
|
||||
'$prefix${it.substringAfter('RESOLUTION=').substringAfter('x').substringBefore(',')}p ';
|
||||
final videoUrl =
|
||||
masterBase + it.substringAfter('\n').substringBefore('\n');
|
||||
videoList
|
||||
.add(Video(videoUrl, quality, videoUrl, headers: playlistHeaders));
|
||||
});
|
||||
|
||||
return videoList;
|
||||
}
|
||||
}
|
||||
|
||||
String? _evalJs(String script) {
|
||||
final jsPacker = JSPacker(script);
|
||||
return jsPacker.unpack();
|
||||
}
|
||||
|
|
@ -16,12 +16,12 @@ class VoeExtractor {
|
|||
if (script.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final videoUrl =
|
||||
script.first!.substringAfter("hls': '").substringBefore("'");
|
||||
final resolution =
|
||||
script.first!.substringAfter("video_height': ").substringBefore(",");
|
||||
final qualityStr = quality ?? "VoeCDN(${resolution}p)";
|
||||
|
||||
return [Video(videoUrl, qualityStr, videoUrl)];
|
||||
} catch (_) {
|
||||
return [];
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ Future<List<MangaModel?>> getLatestUpdatesManga(
|
|||
lang: value.lang,
|
||||
sourceId: value.sourceId,
|
||||
dateFormat: value.dateFormat,
|
||||
hasNextPage: value.hasNextPage ?? true,
|
||||
dateFormatLocale: value.dateFormatLocale);
|
||||
newManga.add(newMangaa);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ Future<List<MangaModel?>> getPopularManga(
|
|||
lang: value.lang,
|
||||
status: value.status,
|
||||
dateFormat: value.dateFormat,
|
||||
hasNextPage: value.hasNextPage ?? true,
|
||||
dateFormatLocale: value.dateFormatLocale);
|
||||
newManga.add(newMangaa);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ Future<List<MangaModel?>> searchManga(
|
|||
apiUrl: value.apiUrl,
|
||||
lang: value.lang,
|
||||
sourceId: value.sourceId,
|
||||
hasNextPage: value.hasNextPage ?? true,
|
||||
dateFormat: value.dateFormat,
|
||||
dateFormatLocale: value.dateFormatLocale);
|
||||
newManga.add(newMangaa);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@ extension StringExtensions on String {
|
|||
|
||||
return substring(0, endIndex);
|
||||
}
|
||||
|
||||
String substringBeforeLast(String pattern) {
|
||||
final endIndex = lastIndexOf(pattern);
|
||||
if (endIndex == -1) return substring(0);
|
||||
|
||||
return substring(0, endIndex);
|
||||
}
|
||||
}
|
||||
|
||||
extension LetExtension<T> on T {
|
||||
|
|
|
|||
|
|
@ -31,10 +31,14 @@ String regCustomMatcher(
|
|||
String source,
|
||||
int group,
|
||||
) {
|
||||
RegExp exp = RegExp(source);
|
||||
Iterable<Match> matches = exp.allMatches(input);
|
||||
String? firstMatch = matches.first.group(group);
|
||||
return firstMatch!;
|
||||
try {
|
||||
RegExp exp = RegExp(source);
|
||||
Iterable<Match> matches = exp.allMatches(input);
|
||||
String? firstMatch = matches.first.group(group);
|
||||
return firstMatch!;
|
||||
} catch (_) {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
String padIndex(int index) {
|
||||
|
|
|
|||
16
pubspec.lock
16
pubspec.lock
|
|
@ -765,6 +765,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.7"
|
||||
js_packer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: js_packer
|
||||
sha256: f45ffa90165a810d7134f0b96b54068e4aac9d80a8b181eafa3978ec6dbc66a3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.5"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1157,6 +1165,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.5"
|
||||
random_string:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: random_string
|
||||
sha256: "03b52435aae8cbdd1056cf91bfc5bf845e9706724dd35ae2e99fa14a1ef79d02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
rfc_6901:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ dependencies:
|
|||
media_kit_libs_android_video: ^1.0.6
|
||||
media_kit_libs_ios_video: ^1.0.4
|
||||
loadmore: ^2.1.0
|
||||
random_string: ^2.3.1
|
||||
js_packer: ^0.0.5
|
||||
|
||||
|
||||
cupertino_icons: ^1.0.2
|
||||
|
|
|
|||
Loading…
Reference in a new issue