import 'dart:async'; import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; import 'dart:ui'; import 'package:extended_image/extended_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart'; import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart'; import 'package:mangayomi/utils/headers.dart'; import 'package:mangayomi/utils/reg_exp_matcher.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as p; extension FileFormatter on num { String formattedFileSize({bool base1024 = true}) { final base = base1024 ? 1024 : 1000; if (this <= 0) return "0"; final units = ["B", "kB", "MB", "GB", "TB"]; int digitGroups = (log(this) / log(base)).round(); return "${NumberFormat("#,##0.#").format(this / pow(base, digitGroups))} ${units[digitGroups]}"; } } extension LetExtension on T { R let(R Function(T) block) { return block(this); } } extension MedianExtension on List { int median() { var middle = length ~/ 2; if (length % 2 == 1) { return this[middle]; } else { return ((this[middle - 1] + this[middle]) / 2).round(); } } int arithmeticMean() { return isNotEmpty ? (reduce((e1, e2) => e1 + e2) / length).round() : 0; } } extension ImageProviderExtension on ImageProvider { Future getBytes( BuildContext context, { ImageByteFormat format = ImageByteFormat.png, }) async { final imageStream = resolve(createLocalImageConfiguration(context)); final Completer completer = Completer(); final ImageStreamListener listener = ImageStreamListener(( imageInfo, synchronousCall, ) async { final bytes = await imageInfo.image.toByteData(format: format); if (!completer.isCompleted) { completer.complete(bytes?.buffer.asUint8List()); } }); imageStream.addListener(listener); final imageBytes = await completer.future; imageStream.removeListener(listener); return imageBytes; } } extension UChapDataPreloadExtensions on UChapDataPreload { Future get getImageBytes async { Uint8List? imageBytes; if (archiveImage != null) { imageBytes = archiveImage; } else if (isLocale == true && directory != null && index != null) { imageBytes = File( p.join(directory!.path, "${padIndex(index!)}.jpg"), ).readAsBytesSync(); } else { File? cachedImage; if (pageUrl != null) { cachedImage = await _getCachedImageFile(pageUrl!.url); if (cachedImage == null) { await Future.delayed(const Duration(seconds: 3)); cachedImage = await _getCachedImageFile(pageUrl!.url); } } imageBytes = cachedImage?.readAsBytesSync(); } return imageBytes; } ImageProvider getImageProvider( WidgetRef ref, bool showCloudFlareError, ) { final data = this; if (data.isTransitionPage) { return const AssetImage('assets/transparent.png') as ImageProvider; } final isLocale = data.isLocale!; final archiveImage = data.archiveImage; final cropBorders = ref.watch(cropBordersStateProvider); return cropBorders && data.cropImage != null ? ExtendedMemoryImageProvider(data.cropImage!) : (isLocale ? archiveImage != null ? ExtendedMemoryImageProvider(archiveImage) : ExtendedFileImageProvider( File( p.join( data.directory!.path, "${padIndex(data.index!)}.jpg", ), ), ) : CustomExtendedNetworkImageProvider( data.pageUrl!.url.trim().trimLeft().trimRight(), cache: true, cacheMaxAge: const Duration(days: 7), showCloudFlareError: showCloudFlareError, imageCacheFolderName: "cacheimagemanga", headers: { ...data.pageUrl!.headers ?? {}, ...ref.watch( headersProvider( source: data.chapter!.manga.value!.source!, lang: data.chapter!.manga.value!.lang!, sourceId: data.chapter!.manga.value!.sourceId, ), ), }, )) as ImageProvider; } } Future _getCachedImageFile(String url, {String? cacheKey}) async { try { final String key = cacheKey ?? keyToMd5(url); final Directory cacheImagesDirectory = Directory( join( (await getTemporaryDirectory()).path, 'Mangayomi', 'cacheimagemanga', ), ); if (cacheImagesDirectory.existsSync()) { await for (final FileSystemEntity file in cacheImagesDirectory.list()) { if (file.path.endsWith(key)) { return File(file.path); } } } } catch (_) { return null; } return null; }