mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-03-11 21:35:32 +00:00
add CBZ/ZIP/CBT/TAR support for local reading
This commit is contained in:
parent
7fe3d71ab2
commit
d4cf5904bd
12 changed files with 1890 additions and 44 deletions
205
lib/modules/local_reader/local_reader_screen.dart
Normal file
205
lib/modules/local_reader/local_reader_screen.dart
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:mangayomi/modules/local_reader/models/models.dart';
|
||||
import 'package:mangayomi/modules/local_reader/providers/local_reader_providers.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/utils/media_query.dart';
|
||||
|
||||
class LocalReaderScreen extends ConsumerStatefulWidget {
|
||||
const LocalReaderScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState createState() => _LocalReaderScreenState();
|
||||
}
|
||||
|
||||
class _LocalReaderScreenState extends ConsumerState<LocalReaderScreen> {
|
||||
List<LocalArchive> images = [];
|
||||
bool isLoading = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Local Reader'),
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
//File
|
||||
FilePickerResult? result = await FilePicker.platform
|
||||
.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: [
|
||||
'cbz',
|
||||
'zip',
|
||||
'cbt',
|
||||
'tar'
|
||||
]);
|
||||
if (result != null) {
|
||||
//File
|
||||
final ddd = await ref.watch(
|
||||
getArchiveDataFromFileProvider(
|
||||
result.files.first.path!)
|
||||
.future);
|
||||
|
||||
setState(() {
|
||||
images.add(ddd);
|
||||
isLoading = false;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.file_open)),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
//Directory
|
||||
String? result =
|
||||
await FilePicker.platform.getDirectoryPath();
|
||||
|
||||
if (result != null) {
|
||||
//Directory
|
||||
final ddd = await ref.watch(
|
||||
getArchiveDataFromDirectoryProvider(result)
|
||||
.future);
|
||||
setState(() {
|
||||
images = ddd;
|
||||
isLoading = false;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.create_new_folder_rounded)),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
childAspectRatio: 0.68, crossAxisCount: 3),
|
||||
itemCount: images.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
context.push("/localReaderReaderView",
|
||||
extra: images[index]);
|
||||
},
|
||||
child: Ink.image(
|
||||
height: 200,
|
||||
fit: BoxFit.cover,
|
||||
image: MemoryImage(images[index].coverImage!),
|
||||
child: Container(
|
||||
height: 70,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
Colors.black.withOpacity(0.6)
|
||||
],
|
||||
stops: const [0, 1],
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
images[index].name!,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white,
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(0.5, 0.9),
|
||||
blurRadius: 3.0)
|
||||
],
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius:
|
||||
BorderRadius.circular(5)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2),
|
||||
child: Text(
|
||||
getTypeExtension(images[index]
|
||||
.extensionType!)
|
||||
.toUpperCase(),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 10),
|
||||
),
|
||||
)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isLoading)
|
||||
Container(
|
||||
width: mediaWidth(context, 1),
|
||||
height: mediaHeight(context, 1),
|
||||
color: Colors.black45,
|
||||
child: UnconstrainedBox(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
height: 200,
|
||||
width: 200,
|
||||
child: const Center(child: ProgressCenter())),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
20
lib/modules/local_reader/models/models.dart
Normal file
20
lib/modules/local_reader/models/models.dart
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class LocalArchive {
|
||||
String? name;
|
||||
|
||||
Uint8List? coverImage;
|
||||
|
||||
List<LocalImage>? images = [];
|
||||
|
||||
LocalExtensionType? extensionType;
|
||||
|
||||
String? path;
|
||||
}
|
||||
|
||||
enum LocalExtensionType { cbz, zip, cbt, tar }
|
||||
|
||||
class LocalImage {
|
||||
String? name;
|
||||
Uint8List? image;
|
||||
}
|
||||
121
lib/modules/local_reader/providers/local_reader_providers.dart
Normal file
121
lib/modules/local_reader/providers/local_reader_providers.dart
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import 'dart:io';
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mangayomi/modules/local_reader/models/models.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'local_reader_providers.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<List<LocalArchive>> getArchiveDataFromDirectory(
|
||||
GetArchiveDataFromDirectoryRef ref, String path) async {
|
||||
return compute(_extract, path);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<LocalArchive> getArchiveDataFromFile(
|
||||
GetArchiveDataFromFileRef ref, String path) async {
|
||||
return compute(_extractArchive, path);
|
||||
}
|
||||
|
||||
List<LocalArchive> _extract(String data) {
|
||||
return _searchForArchive(Directory(data));
|
||||
}
|
||||
|
||||
List<LocalArchive> _list = [];
|
||||
List<LocalArchive> _searchForArchive(Directory dir) {
|
||||
List<FileSystemEntity> entities = dir.listSync();
|
||||
for (FileSystemEntity entity in entities) {
|
||||
if (entity is Directory) {
|
||||
_searchForArchive(entity);
|
||||
} else if (entity is File) {
|
||||
String path = entity.path;
|
||||
if (_isArchiveFile(path)) {
|
||||
final dd = _extractArchive(path);
|
||||
_list.add(dd);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _list;
|
||||
}
|
||||
|
||||
bool _isImageFile(String path) {
|
||||
List<String> imageExtensions = ['.png', '.jpg', '.jpeg'];
|
||||
String extension = path.toLowerCase();
|
||||
for (String imageExtension in imageExtensions) {
|
||||
if (extension.endsWith(imageExtension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _isArchiveFile(String path) {
|
||||
List<String> imageExtensions = ['.cbz', '.zip', 'cbt', 'tar'];
|
||||
String extension = path.toLowerCase();
|
||||
for (String imageExtension in imageExtensions) {
|
||||
if (extension.endsWith(imageExtension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
LocalArchive _extractArchive(String path) {
|
||||
final bytes = File(path).readAsBytesSync();
|
||||
final localArchive = LocalArchive()
|
||||
..path = path
|
||||
..extensionType =
|
||||
setTypeExtension(path.split('/').last.split("\\").last.split(".").last)
|
||||
..name = path
|
||||
.split('/')
|
||||
.last
|
||||
.split("\\")
|
||||
.last
|
||||
.replaceAll(RegExp(r'\.(cbz|zip|cbt|tar)'), '');
|
||||
Archive? archive;
|
||||
final extensionType = localArchive.extensionType;
|
||||
if (extensionType == LocalExtensionType.cbt ||
|
||||
extensionType == LocalExtensionType.tar) {
|
||||
archive = TarDecoder().decodeBytes(bytes);
|
||||
} else {
|
||||
archive = ZipDecoder().decodeBytes(bytes);
|
||||
}
|
||||
|
||||
for (final file in archive) {
|
||||
final filename = file.name;
|
||||
if (file.isFile) {
|
||||
if (_isImageFile(filename)) {
|
||||
if (filename.contains("cover")) {
|
||||
final data = file.content as Uint8List;
|
||||
localArchive.coverImage = Uint8List.fromList(data);
|
||||
} else {
|
||||
final data = file.content as Uint8List;
|
||||
localArchive.images!.add(LocalImage()
|
||||
..image = Uint8List.fromList(data)
|
||||
..name = filename.split('/').last.split("\\").last);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
localArchive.images!.sort((a, b) => a.name!.compareTo(b.name!));
|
||||
localArchive.coverImage ??= localArchive.images!.first.image;
|
||||
return localArchive;
|
||||
}
|
||||
|
||||
String getTypeExtension(LocalExtensionType type) {
|
||||
return switch (type) {
|
||||
LocalExtensionType.cbt => type.name,
|
||||
LocalExtensionType.zip => type.name,
|
||||
LocalExtensionType.tar => type.name,
|
||||
_ => type.name,
|
||||
};
|
||||
}
|
||||
|
||||
LocalExtensionType setTypeExtension(String extension) {
|
||||
return switch (extension) {
|
||||
"cbt" => LocalExtensionType.cbt,
|
||||
"zip" => LocalExtensionType.zip,
|
||||
"tar" => LocalExtensionType.tar,
|
||||
_ => LocalExtensionType.cbz,
|
||||
};
|
||||
}
|
||||
200
lib/modules/local_reader/providers/local_reader_providers.g.dart
Normal file
200
lib/modules/local_reader/providers/local_reader_providers.g.dart
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'local_reader_providers.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$getArchiveDataFromDirectoryHash() =>
|
||||
r'fb85bd2b43ae73f083bdfa0760d8185ef989dd09';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
typedef GetArchiveDataFromDirectoryRef
|
||||
= AutoDisposeFutureProviderRef<List<LocalArchive>>;
|
||||
|
||||
/// See also [getArchiveDataFromDirectory].
|
||||
@ProviderFor(getArchiveDataFromDirectory)
|
||||
const getArchiveDataFromDirectoryProvider = GetArchiveDataFromDirectoryFamily();
|
||||
|
||||
/// See also [getArchiveDataFromDirectory].
|
||||
class GetArchiveDataFromDirectoryFamily
|
||||
extends Family<AsyncValue<List<LocalArchive>>> {
|
||||
/// See also [getArchiveDataFromDirectory].
|
||||
const GetArchiveDataFromDirectoryFamily();
|
||||
|
||||
/// See also [getArchiveDataFromDirectory].
|
||||
GetArchiveDataFromDirectoryProvider call(
|
||||
String path,
|
||||
) {
|
||||
return GetArchiveDataFromDirectoryProvider(
|
||||
path,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
GetArchiveDataFromDirectoryProvider getProviderOverride(
|
||||
covariant GetArchiveDataFromDirectoryProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.path,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'getArchiveDataFromDirectoryProvider';
|
||||
}
|
||||
|
||||
/// See also [getArchiveDataFromDirectory].
|
||||
class GetArchiveDataFromDirectoryProvider
|
||||
extends AutoDisposeFutureProvider<List<LocalArchive>> {
|
||||
/// See also [getArchiveDataFromDirectory].
|
||||
GetArchiveDataFromDirectoryProvider(
|
||||
this.path,
|
||||
) : super.internal(
|
||||
(ref) => getArchiveDataFromDirectory(
|
||||
ref,
|
||||
path,
|
||||
),
|
||||
from: getArchiveDataFromDirectoryProvider,
|
||||
name: r'getArchiveDataFromDirectoryProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$getArchiveDataFromDirectoryHash,
|
||||
dependencies: GetArchiveDataFromDirectoryFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
GetArchiveDataFromDirectoryFamily._allTransitiveDependencies,
|
||||
);
|
||||
|
||||
final String path;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is GetArchiveDataFromDirectoryProvider && other.path == path;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, path.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
String _$getArchiveDataFromFileHash() =>
|
||||
r'e5dc60fea6c36346c47542c141703bb027173215';
|
||||
typedef GetArchiveDataFromFileRef = AutoDisposeFutureProviderRef<LocalArchive>;
|
||||
|
||||
/// See also [getArchiveDataFromFile].
|
||||
@ProviderFor(getArchiveDataFromFile)
|
||||
const getArchiveDataFromFileProvider = GetArchiveDataFromFileFamily();
|
||||
|
||||
/// See also [getArchiveDataFromFile].
|
||||
class GetArchiveDataFromFileFamily extends Family<AsyncValue<LocalArchive>> {
|
||||
/// See also [getArchiveDataFromFile].
|
||||
const GetArchiveDataFromFileFamily();
|
||||
|
||||
/// See also [getArchiveDataFromFile].
|
||||
GetArchiveDataFromFileProvider call(
|
||||
String path,
|
||||
) {
|
||||
return GetArchiveDataFromFileProvider(
|
||||
path,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
GetArchiveDataFromFileProvider getProviderOverride(
|
||||
covariant GetArchiveDataFromFileProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.path,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'getArchiveDataFromFileProvider';
|
||||
}
|
||||
|
||||
/// See also [getArchiveDataFromFile].
|
||||
class GetArchiveDataFromFileProvider
|
||||
extends AutoDisposeFutureProvider<LocalArchive> {
|
||||
/// See also [getArchiveDataFromFile].
|
||||
GetArchiveDataFromFileProvider(
|
||||
this.path,
|
||||
) : super.internal(
|
||||
(ref) => getArchiveDataFromFile(
|
||||
ref,
|
||||
path,
|
||||
),
|
||||
from: getArchiveDataFromFileProvider,
|
||||
name: r'getArchiveDataFromFileProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$getArchiveDataFromFileHash,
|
||||
dependencies: GetArchiveDataFromFileFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
GetArchiveDataFromFileFamily._allTransitiveDependencies,
|
||||
);
|
||||
|
||||
final String path;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is GetArchiveDataFromFileProvider && other.path == path;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, path.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions
|
||||
938
lib/modules/local_reader/reader/local_reader_reader_view.dart
Normal file
938
lib/modules/local_reader/reader/local_reader_reader_view.dart
Normal file
|
|
@ -0,0 +1,938 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'package:draggable_menu/draggable_menu.dart';
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/modules/local_reader/models/models.dart';
|
||||
import 'package:mangayomi/utils/image_detail_info.dart';
|
||||
import 'package:mangayomi/utils/media_query.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/circular_progress_indicator_animate_rotate.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:photo_view/photo_view_gallery.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
typedef DoubleClickAnimationListener = void Function();
|
||||
|
||||
class LocalReaderReaderView extends ConsumerWidget {
|
||||
final LocalArchive localArchive;
|
||||
const LocalReaderReaderView({
|
||||
super.key,
|
||||
required this.localArchive,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky,
|
||||
overlays: []);
|
||||
|
||||
return MangaChapterPageGallery(localArchive: localArchive);
|
||||
}
|
||||
}
|
||||
|
||||
class MangaChapterPageGallery extends ConsumerStatefulWidget {
|
||||
const MangaChapterPageGallery({super.key, required this.localArchive});
|
||||
final LocalArchive localArchive;
|
||||
|
||||
@override
|
||||
ConsumerState createState() {
|
||||
return _MangaChapterPageGalleryState();
|
||||
}
|
||||
}
|
||||
|
||||
class _MangaChapterPageGalleryState
|
||||
extends ConsumerState<MangaChapterPageGallery>
|
||||
with TickerProviderStateMixin {
|
||||
late final ItemScrollController _itemScrollController =
|
||||
ItemScrollController();
|
||||
late AnimationController _scaleAnimationController;
|
||||
late Animation<double> _animation;
|
||||
late int _currentIndex = 0;
|
||||
@override
|
||||
void dispose() {
|
||||
_rebuildDetail.close();
|
||||
_doubleClickAnimationController.dispose();
|
||||
clearGestureDetailsCache();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool animatePageTransitions =
|
||||
isar.settings.getSync(227)!.animatePageTransitions!;
|
||||
Duration? _doubleTapAnimationDuration() {
|
||||
int doubleTapAnimationValue =
|
||||
isar.settings.getSync(227)!.doubleTapAnimationSpeed!;
|
||||
if (doubleTapAnimationValue == 0) {
|
||||
return const Duration(milliseconds: 10);
|
||||
} else if (doubleTapAnimationValue == 1) {
|
||||
return const Duration(milliseconds: 800);
|
||||
}
|
||||
return const Duration(milliseconds: 200);
|
||||
}
|
||||
|
||||
Future setIndex(int index) async {
|
||||
setState(() {
|
||||
_currentIndex = index;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_doubleClickAnimationController = AnimationController(
|
||||
duration: _doubleTapAnimationDuration(), vsync: this);
|
||||
|
||||
_scaleAnimationController = AnimationController(
|
||||
duration: _doubleTapAnimationDuration(), vsync: this);
|
||||
_animation = Tween(begin: 1.0, end: 2.0).animate(
|
||||
CurvedAnimation(curve: Curves.ease, parent: _scaleAnimationController));
|
||||
_animation.addListener(() => _photoViewController.scale = _animation.value);
|
||||
_initCurrentIndex();
|
||||
_itemPositionsListener.itemPositions.addListener(_readProgressListener);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
_readProgressListener() {
|
||||
var posIndex = _itemPositionsListener.itemPositions.value.first.index;
|
||||
if (posIndex >= 0 && posIndex < widget.localArchive.images!.length) {
|
||||
if (_currentIndex != posIndex) {
|
||||
setState(() {
|
||||
_currentIndex = posIndex;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_initCurrentIndex() async {
|
||||
await Future.delayed(const Duration(milliseconds: 1));
|
||||
_selectedValue = ReaderMode.vertical;
|
||||
_(_selectedValue!, true);
|
||||
}
|
||||
|
||||
void _onPageChanged(int index) {
|
||||
setState(() {
|
||||
_currentIndex = index;
|
||||
});
|
||||
if (_imageDetailY != 0) {
|
||||
_imageDetailY = 0;
|
||||
_rebuildDetail.sink.add(_imageDetailY);
|
||||
}
|
||||
}
|
||||
|
||||
void _onBtnTapped(int index, bool isPrev, {bool isSlide = false}) {
|
||||
if (isPrev) {
|
||||
if (_selectedValue == ReaderMode.verticalContinuous ||
|
||||
_selectedValue == ReaderMode.webtoon) {
|
||||
if (index != -1) {
|
||||
if (isSlide) {
|
||||
_itemScrollController.jumpTo(
|
||||
index: index,
|
||||
);
|
||||
} else {
|
||||
animatePageTransitions
|
||||
? _itemScrollController.scrollTo(
|
||||
curve: Curves.ease,
|
||||
index: index,
|
||||
duration: Duration(milliseconds: isSlide ? 2 : 150))
|
||||
: _itemScrollController.jumpTo(
|
||||
index: index,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (index != -1) {
|
||||
if (_extendedController.hasClients) {
|
||||
setState(() {
|
||||
_isZoom = false;
|
||||
});
|
||||
animatePageTransitions
|
||||
? _extendedController.animateToPage(index,
|
||||
duration: Duration(milliseconds: isSlide ? 2 : 150),
|
||||
curve: Curves.ease)
|
||||
: _extendedController.jumpToPage(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_selectedValue == ReaderMode.verticalContinuous ||
|
||||
_selectedValue == ReaderMode.webtoon) {
|
||||
if (widget.localArchive.images!.length != index) {
|
||||
if (isSlide) {
|
||||
_itemScrollController.jumpTo(
|
||||
index: index,
|
||||
);
|
||||
} else {
|
||||
animatePageTransitions
|
||||
? _itemScrollController.scrollTo(
|
||||
curve: Curves.ease,
|
||||
index: index,
|
||||
duration: Duration(milliseconds: isSlide ? 2 : 150))
|
||||
: _itemScrollController.jumpTo(
|
||||
index: index,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (widget.localArchive.images!.length != index) {
|
||||
if (_extendedController.hasClients) {
|
||||
setState(() {
|
||||
_isZoom = false;
|
||||
});
|
||||
animatePageTransitions
|
||||
? _extendedController.animateToPage(index,
|
||||
duration: Duration(milliseconds: isSlide ? 2 : 150),
|
||||
curve: Curves.ease)
|
||||
: _extendedController.jumpToPage(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReaderMode? _selectedValue;
|
||||
bool _isView = false;
|
||||
Alignment _scalePosition = Alignment.center;
|
||||
final PhotoViewController _photoViewController = PhotoViewController();
|
||||
final PhotoViewScaleStateController _photoViewScaleStateController =
|
||||
PhotoViewScaleStateController();
|
||||
|
||||
final ItemPositionsListener _itemPositionsListener =
|
||||
ItemPositionsListener.create();
|
||||
void _onScaleEnd(BuildContext context, ScaleEndDetails details,
|
||||
PhotoViewControllerValue controllerValue) {
|
||||
if (controllerValue.scale! < 1) {
|
||||
_photoViewScaleStateController.reset();
|
||||
}
|
||||
}
|
||||
|
||||
late final _extendedController = ExtendedPageController(
|
||||
initialPage: _currentIndex,
|
||||
shouldIgnorePointerWhenScrolling: false,
|
||||
);
|
||||
|
||||
double get pixelRatio => View.of(context).devicePixelRatio;
|
||||
|
||||
Size get size => View.of(context).physicalSize / pixelRatio;
|
||||
Alignment _computeAlignmentByTapOffset(Offset offset) {
|
||||
return Alignment((offset.dx - size.width / 2) / (size.width / 2),
|
||||
(offset.dy - size.height / 2) / (size.height / 2));
|
||||
}
|
||||
|
||||
void _toggleScale(Offset tapPosition) {
|
||||
setState(() {
|
||||
if (_scaleAnimationController.isAnimating) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_photoViewController.scale == 1.0) {
|
||||
_scalePosition = _computeAlignmentByTapOffset(tapPosition);
|
||||
|
||||
if (_scaleAnimationController.isCompleted) {
|
||||
_scaleAnimationController.reset();
|
||||
}
|
||||
|
||||
_scaleAnimationController.forward();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_photoViewController.scale == 2.0) {
|
||||
_scaleAnimationController.reverse();
|
||||
return;
|
||||
}
|
||||
|
||||
_photoViewScaleStateController.reset();
|
||||
});
|
||||
}
|
||||
|
||||
Axis _scrollDirection = Axis.vertical;
|
||||
bool _isReversHorizontal = false;
|
||||
|
||||
late bool _showPagesNumber = true;
|
||||
_(ReaderMode value, bool isInit) async {
|
||||
if (value == ReaderMode.vertical) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_selectedValue = value;
|
||||
_scrollDirection = Axis.vertical;
|
||||
_isReversHorizontal = false;
|
||||
});
|
||||
if (isInit) {
|
||||
await Future.delayed(const Duration(milliseconds: 30));
|
||||
}
|
||||
_extendedController.jumpToPage(_currentIndex);
|
||||
}
|
||||
} else if (value == ReaderMode.ltr || value == ReaderMode.rtl) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
if (value == ReaderMode.rtl) {
|
||||
_isReversHorizontal = true;
|
||||
} else {
|
||||
_isReversHorizontal = false;
|
||||
}
|
||||
_selectedValue = value;
|
||||
_scrollDirection = Axis.horizontal;
|
||||
});
|
||||
if (isInit) {
|
||||
await Future.delayed(const Duration(milliseconds: 30));
|
||||
}
|
||||
_extendedController.jumpToPage(_currentIndex);
|
||||
}
|
||||
} else {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_selectedValue = value;
|
||||
_isReversHorizontal = false;
|
||||
});
|
||||
if (isInit) {
|
||||
await Future.delayed(const Duration(milliseconds: 30));
|
||||
}
|
||||
_itemScrollController.scrollTo(
|
||||
index: _currentIndex, duration: const Duration(milliseconds: 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Color _backgroundColor(BuildContext context) =>
|
||||
Theme.of(context).scaffoldBackgroundColor.withOpacity(0.9);
|
||||
|
||||
Widget _showMore() {
|
||||
return Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final currentIndex = _currentIndex;
|
||||
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
height: _isView ? 80 : 0,
|
||||
curve: Curves.ease,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: PreferredSize(
|
||||
preferredSize: Size.fromHeight(_isView ? 80 : 0),
|
||||
child: AppBar(
|
||||
centerTitle: false,
|
||||
automaticallyImplyLeading: false,
|
||||
titleSpacing: 0,
|
||||
leading: BackButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
title: ListTile(
|
||||
dense: true,
|
||||
title: SizedBox(
|
||||
width: mediaWidth(context, 0.8),
|
||||
child: Text(
|
||||
'${widget.localArchive.name} ',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
subtitle: SizedBox(
|
||||
width: mediaWidth(context, 0.8),
|
||||
child: Text(
|
||||
"",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
backgroundColor: _backgroundColor(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedContainer(
|
||||
curve: Curves.ease,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
width: mediaWidth(context, 1),
|
||||
height: _isView ? 130 : 0,
|
||||
child: Column(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Transform.scale(
|
||||
scaleX: !_isReversHorizontal ? 1 : -1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Container(
|
||||
height: 70,
|
||||
decoration: BoxDecoration(
|
||||
color: _backgroundColor(context),
|
||||
borderRadius: BorderRadius.circular(25)),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12),
|
||||
child: Transform.scale(
|
||||
scaleX: !_isReversHorizontal ? 1 : -1,
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
child: Text(
|
||||
"${currentIndex + 1} ",
|
||||
style: const TextStyle(
|
||||
fontSize: 15.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Slider(
|
||||
onChanged: (newValue) {
|
||||
_onBtnTapped(newValue.toInt(), true,
|
||||
isSlide: true);
|
||||
},
|
||||
divisions: max(
|
||||
widget.localArchive.images!.length -
|
||||
1,
|
||||
1),
|
||||
value: min(
|
||||
_currentIndex.toDouble(),
|
||||
widget.localArchive.images!.length
|
||||
.toDouble()),
|
||||
min: 0,
|
||||
max: (widget.localArchive.images!
|
||||
.length -
|
||||
1)
|
||||
.toDouble(),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12),
|
||||
child: Transform.scale(
|
||||
scaleX: !_isReversHorizontal ? 1 : -1,
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
child: Text(
|
||||
"${widget.localArchive.images!.length}",
|
||||
style: const TextStyle(
|
||||
fontSize: 15.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Container(
|
||||
height: 65,
|
||||
color: _backgroundColor(context),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
PopupMenuButton(
|
||||
color: Colors.black,
|
||||
child: const Icon(
|
||||
Icons.app_settings_alt_outlined,
|
||||
),
|
||||
onSelected: (value) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_selectedValue = value;
|
||||
});
|
||||
}
|
||||
_(value, true);
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
for (var readerMode in ReaderMode.values)
|
||||
PopupMenuItem(
|
||||
value: readerMode,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check,
|
||||
color: _selectedValue == readerMode
|
||||
? Colors.white
|
||||
: Colors.transparent,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 7,
|
||||
),
|
||||
Text(
|
||||
getReaderModeName(readerMode),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.screen_rotation,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_showModalSettings();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.settings_rounded,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _showPage() {
|
||||
return Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final currentIndex = _currentIndex;
|
||||
return _isView
|
||||
? Container()
|
||||
: _showPagesNumber
|
||||
? Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Text(
|
||||
'${currentIndex + 1} / ${widget.localArchive.images!.length}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12.0,
|
||||
shadows: <Shadow>[
|
||||
Shadow(offset: Offset(0.0, 0.0), blurRadius: 10.0)
|
||||
],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
)
|
||||
: Container();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_isViewFunction() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isView = !_isView;
|
||||
});
|
||||
}
|
||||
if (_isView) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||
overlays: SystemUiOverlay.values);
|
||||
} else {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky,
|
||||
overlays: []);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _gestureRightLeft() {
|
||||
return Consumer(
|
||||
builder: (context, ref, child) {
|
||||
return Row(
|
||||
children: [
|
||||
/// left region
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
if (_isReversHorizontal) {
|
||||
_onBtnTapped(_currentIndex + 1, false);
|
||||
} else {
|
||||
_onBtnTapped(_currentIndex - 1, true);
|
||||
}
|
||||
},
|
||||
onDoubleTapDown: _isVerticalContinous()
|
||||
? (TapDownDetails details) {
|
||||
_toggleScale(details.globalPosition);
|
||||
}
|
||||
: null,
|
||||
onDoubleTap: _isVerticalContinous() ? () {} : null,
|
||||
),
|
||||
),
|
||||
|
||||
/// center region
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
_isViewFunction();
|
||||
},
|
||||
onDoubleTapDown: _isVerticalContinous()
|
||||
? (TapDownDetails details) {
|
||||
_toggleScale(details.globalPosition);
|
||||
}
|
||||
: null,
|
||||
onDoubleTap: _isVerticalContinous() ? () {} : null,
|
||||
),
|
||||
),
|
||||
|
||||
/// right region
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
if (_isReversHorizontal) {
|
||||
_onBtnTapped(_currentIndex - 1, true);
|
||||
} else {
|
||||
_onBtnTapped(_currentIndex + 1, false);
|
||||
}
|
||||
},
|
||||
onDoubleTapDown: _isVerticalContinous()
|
||||
? (TapDownDetails details) {
|
||||
_toggleScale(details.globalPosition);
|
||||
}
|
||||
: null,
|
||||
onDoubleTap: _isVerticalContinous() ? () {} : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _gestureTopBottom() {
|
||||
return Consumer(
|
||||
builder: (context, ref, child) {
|
||||
return Column(
|
||||
children: [
|
||||
/// top region
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
_onBtnTapped(_currentIndex - 1, true);
|
||||
},
|
||||
onDoubleTapDown: _isVerticalContinous()
|
||||
? (TapDownDetails details) {
|
||||
_toggleScale(details.globalPosition);
|
||||
}
|
||||
: null,
|
||||
onDoubleTap: _isVerticalContinous() ? () {} : null,
|
||||
),
|
||||
),
|
||||
|
||||
/// center region
|
||||
Expanded(flex: 5, child: Container()),
|
||||
|
||||
/// bottom region
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
_onBtnTapped(_currentIndex + 1, false);
|
||||
},
|
||||
onDoubleTapDown: _isVerticalContinous()
|
||||
? (TapDownDetails details) {
|
||||
_toggleScale(details.globalPosition);
|
||||
}
|
||||
: null,
|
||||
onDoubleTap: _isVerticalContinous() ? () {} : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
bool _isZoom = false;
|
||||
bool _isVerticalContinous() {
|
||||
return _selectedValue == ReaderMode.verticalContinuous ||
|
||||
_selectedValue == ReaderMode.webtoon;
|
||||
}
|
||||
|
||||
final StreamController<double> _rebuildDetail =
|
||||
StreamController<double>.broadcast();
|
||||
final Map<int, ImageDetailInfo> detailKeys = <int, ImageDetailInfo>{};
|
||||
late AnimationController _doubleClickAnimationController;
|
||||
|
||||
Animation<double>? _doubleClickAnimation;
|
||||
late DoubleClickAnimationListener _doubleClickAnimationListener;
|
||||
List<double> doubleTapScales = <double>[1.0, 2.0];
|
||||
GlobalKey<ExtendedImageSlidePageState> slidePagekey =
|
||||
GlobalKey<ExtendedImageSlidePageState>();
|
||||
double _imageDetailY = 0;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||
overlays: SystemUiOverlay.values);
|
||||
Navigator.pop(context);
|
||||
|
||||
return false;
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
_isVerticalContinous()
|
||||
? PhotoViewGallery.builder(
|
||||
itemCount: 1,
|
||||
builder: (_, __) => PhotoViewGalleryPageOptions.customChild(
|
||||
controller: _photoViewController,
|
||||
scaleStateController: _photoViewScaleStateController,
|
||||
basePosition: _scalePosition,
|
||||
onScaleEnd: _onScaleEnd,
|
||||
child: ScrollablePositionedList.separated(
|
||||
physics: const ClampingScrollPhysics(),
|
||||
minCacheExtent: 8 * (MediaQuery.of(context).size.height),
|
||||
initialScrollIndex: _currentIndex,
|
||||
itemCount: widget.localArchive.images!.length,
|
||||
itemScrollController: _itemScrollController,
|
||||
itemPositionsListener: _itemPositionsListener,
|
||||
itemBuilder: (context, index) => GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onDoubleTapDown: (TapDownDetails details) {
|
||||
_toggleScale(details.globalPosition);
|
||||
},
|
||||
onDoubleTap: () {},
|
||||
child: Image.memory(
|
||||
widget.localArchive.images![index].image!)),
|
||||
separatorBuilder: (_, __) => Divider(
|
||||
color: Colors.black,
|
||||
height: _selectedValue == ReaderMode.webtoon ? 0 : 6),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Material(
|
||||
color: Colors.black,
|
||||
shadowColor: Colors.black,
|
||||
child: ExtendedImageGesturePageView.builder(
|
||||
controller: _extendedController,
|
||||
scrollDirection: _scrollDirection,
|
||||
reverse: _isReversHorizontal,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
preloadPagesCount:
|
||||
_isZoom ? 0 : widget.localArchive.images!.length,
|
||||
canScrollPage: (GestureDetails? gestureDetails) {
|
||||
return gestureDetails != null
|
||||
? !(gestureDetails.totalScale! > 1.0)
|
||||
: true;
|
||||
},
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ExtendedImage.memory(
|
||||
widget.localArchive.images![index].image!,
|
||||
clearMemoryCacheWhenDispose: true,
|
||||
enableMemoryCache: false,
|
||||
mode: ExtendedImageMode.gesture,
|
||||
loadStateChanged: (ExtendedImageState state) {
|
||||
if (state.extendedImageLoadState ==
|
||||
LoadState.loading) {
|
||||
final ImageChunkEvent? loadingProgress =
|
||||
state.loadingProgress;
|
||||
final double progress =
|
||||
loadingProgress?.expectedTotalBytes != null
|
||||
? loadingProgress!.cumulativeBytesLoaded /
|
||||
loadingProgress.expectedTotalBytes!
|
||||
: 0;
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
height: mediaHeight(context, 0.8),
|
||||
child: CircularProgressIndicatorAnimateRotate(
|
||||
progress: progress),
|
||||
);
|
||||
}
|
||||
if (state.extendedImageLoadState ==
|
||||
LoadState.completed) {
|
||||
return StreamBuilder<double>(
|
||||
builder: (BuildContext context,
|
||||
AsyncSnapshot<double> data) {
|
||||
return ExtendedImageGesture(
|
||||
state,
|
||||
canScaleImage: (_) => _imageDetailY == 0,
|
||||
imageBuilder: (Widget image) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Positioned.fill(
|
||||
top: _imageDetailY,
|
||||
bottom: -_imageDetailY,
|
||||
child: image,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
initialData: _imageDetailY,
|
||||
stream: _rebuildDetail.stream,
|
||||
);
|
||||
}
|
||||
if (state.extendedImageLoadState ==
|
||||
LoadState.failed) {
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
height: mediaHeight(context, 0.8),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
state.reLoadImage();
|
||||
},
|
||||
child: const Icon(
|
||||
Icons.replay_outlined,
|
||||
size: 30,
|
||||
)),
|
||||
],
|
||||
));
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
initGestureConfigHandler: (ExtendedImageState state) {
|
||||
double? initialScale = 1.0;
|
||||
final size = MediaQuery.of(context).size;
|
||||
if (state.extendedImageInfo != null) {
|
||||
initialScale = initScale(
|
||||
size: size,
|
||||
initialScale: initialScale,
|
||||
imageSize: Size(
|
||||
state.extendedImageInfo!.image.width
|
||||
.toDouble(),
|
||||
state.extendedImageInfo!.image.height
|
||||
.toDouble()));
|
||||
}
|
||||
return GestureConfig(
|
||||
inertialSpeed: 200,
|
||||
inPageView: true,
|
||||
initialScale: initialScale!,
|
||||
maxScale: 8,
|
||||
animationMaxScale: 8,
|
||||
initialAlignment: InitialAlignment.center,
|
||||
cacheGesture: true,
|
||||
hitTestBehavior: HitTestBehavior.translucent,
|
||||
);
|
||||
},
|
||||
onDoubleTap: (ExtendedImageGestureState state) {
|
||||
final Offset? pointerDownPosition =
|
||||
state.pointerDownPosition;
|
||||
final double? begin =
|
||||
state.gestureDetails!.totalScale;
|
||||
double end;
|
||||
|
||||
//remove old
|
||||
_doubleClickAnimation
|
||||
?.removeListener(_doubleClickAnimationListener);
|
||||
|
||||
//stop pre
|
||||
_doubleClickAnimationController.stop();
|
||||
|
||||
//reset to use
|
||||
_doubleClickAnimationController.reset();
|
||||
|
||||
if (begin == doubleTapScales[0]) {
|
||||
setState(() {
|
||||
_isZoom = true;
|
||||
});
|
||||
end = doubleTapScales[1];
|
||||
} else {
|
||||
setState(() {
|
||||
_isZoom = false;
|
||||
});
|
||||
end = doubleTapScales[0];
|
||||
}
|
||||
|
||||
_doubleClickAnimationListener = () {
|
||||
state.handleDoubleTap(
|
||||
scale: _doubleClickAnimation!.value,
|
||||
doubleTapPosition: pointerDownPosition);
|
||||
};
|
||||
|
||||
_doubleClickAnimation = Tween(
|
||||
begin: begin, end: end)
|
||||
.animate(CurvedAnimation(
|
||||
curve: Curves.ease,
|
||||
parent: _doubleClickAnimationController));
|
||||
|
||||
_doubleClickAnimation!
|
||||
.addListener(_doubleClickAnimationListener);
|
||||
|
||||
_doubleClickAnimationController.forward();
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: widget.localArchive.images!.length,
|
||||
onPageChanged: _onPageChanged)),
|
||||
_gestureRightLeft(),
|
||||
_gestureTopBottom(),
|
||||
_showMore(),
|
||||
_showPage(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_showModalSettings() {
|
||||
DraggableMenu.open(
|
||||
context,
|
||||
DraggableMenu(
|
||||
ui: ClassicDraggableMenu(barItem: Container()),
|
||||
expandable: false,
|
||||
maxHeight: mediaHeight(context, 0.4),
|
||||
fastDrag: false,
|
||||
minimizeBeforeFastDrag: false,
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'Settings',
|
||||
style: TextStyle(
|
||||
fontSize: 17, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
SwitchListTile(
|
||||
dense: true,
|
||||
title: const Text('Show Page Number'),
|
||||
value: _showPagesNumber,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_showPagesNumber = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ class _MainScreenState extends State<MainScreen> {
|
|||
final route = GoRouter.of(context);
|
||||
int currentIndex = route.location == '/library'
|
||||
? 0
|
||||
: route.location == '/updates'
|
||||
: route.location == '/localReader'
|
||||
? 1
|
||||
: route.location == '/history'
|
||||
? 2
|
||||
|
|
@ -79,7 +79,7 @@ class _MainScreenState extends State<MainScreen> {
|
|||
width: isLongPressed
|
||||
? 0
|
||||
: route.location != '/library' &&
|
||||
route.location != '/updates' &&
|
||||
route.location != '/localReader' &&
|
||||
route.location != '/history' &&
|
||||
route.location != '/browse' &&
|
||||
route.location != '/more'
|
||||
|
|
@ -106,14 +106,14 @@ class _MainScreenState extends State<MainScreen> {
|
|||
child: Text('Library'))),
|
||||
NavigationRailDestination(
|
||||
selectedIcon: Icon(
|
||||
Icons.new_releases,
|
||||
Icons.library_books,
|
||||
),
|
||||
icon: Icon(
|
||||
Icons.new_releases_outlined,
|
||||
Icons.library_books_outlined,
|
||||
),
|
||||
label: Padding(
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
child: Text('Updates'))),
|
||||
child: Text('Local Reader'))),
|
||||
NavigationRailDestination(
|
||||
selectedIcon: Icon(
|
||||
Icons.history,
|
||||
|
|
@ -158,7 +158,7 @@ class _MainScreenState extends State<MainScreen> {
|
|||
if (newIndex == 0) {
|
||||
route.go('/library');
|
||||
} else if (newIndex == 1) {
|
||||
route.go('/updates');
|
||||
route.go('/localReader');
|
||||
} else if (newIndex == 2) {
|
||||
route.go('/history');
|
||||
} else if (newIndex == 3) {
|
||||
|
|
@ -186,7 +186,7 @@ class _MainScreenState extends State<MainScreen> {
|
|||
height: isLongPressed
|
||||
? 0
|
||||
: route.location != '/library' &&
|
||||
route.location != '/updates' &&
|
||||
route.location != '/localReader' &&
|
||||
route.location != '/history' &&
|
||||
route.location != '/browse' &&
|
||||
route.location != '/more'
|
||||
|
|
@ -212,12 +212,12 @@ class _MainScreenState extends State<MainScreen> {
|
|||
label: 'Library'),
|
||||
NavigationDestination(
|
||||
selectedIcon: Icon(
|
||||
Icons.new_releases,
|
||||
Icons.library_books,
|
||||
),
|
||||
icon: Icon(
|
||||
Icons.new_releases_outlined,
|
||||
Icons.library_books_outlined,
|
||||
),
|
||||
label: 'Updates'),
|
||||
label: 'Local Reader'),
|
||||
NavigationDestination(
|
||||
selectedIcon: Icon(
|
||||
Icons.history,
|
||||
|
|
@ -252,7 +252,7 @@ class _MainScreenState extends State<MainScreen> {
|
|||
if (newIndex == 0) {
|
||||
route.go('/library');
|
||||
} else if (newIndex == 1) {
|
||||
route.go('/updates');
|
||||
route.go('/localReader');
|
||||
} else if (newIndex == 2) {
|
||||
route.go('/history');
|
||||
} else if (newIndex == 3) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:go_router/go_router.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/manga_type.dart';
|
||||
import 'package:mangayomi/modules/local_reader/local_reader_screen.dart';
|
||||
import 'package:mangayomi/modules/local_reader/models/models.dart';
|
||||
import 'package:mangayomi/modules/local_reader/reader/local_reader_reader_view.dart';
|
||||
import 'package:mangayomi/modules/webview/webview.dart';
|
||||
import 'package:mangayomi/modules/browse/browse_screen.dart';
|
||||
import 'package:mangayomi/modules/browse/extension/extension_lang.dart';
|
||||
|
|
@ -23,7 +26,6 @@ import 'package:mangayomi/modules/more/settings/browse/browse_screen.dart';
|
|||
import 'package:mangayomi/modules/more/settings/general/general_screen.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart';
|
||||
import 'package:mangayomi/modules/more/settings/settings_screen.dart';
|
||||
import 'package:mangayomi/modules/updates/updates_screen.dart';
|
||||
|
||||
final routerProvider = Provider<GoRouter>((ref) {
|
||||
final router = AsyncRouterNotifier();
|
||||
|
|
@ -51,12 +53,12 @@ class AsyncRouterNotifier extends ChangeNotifier {
|
|||
),
|
||||
),
|
||||
GoRoute(
|
||||
name: "updates",
|
||||
path: '/updates',
|
||||
builder: (context, state) => const UpdatesScreen(),
|
||||
name: "localReader",
|
||||
path: '/localReader',
|
||||
builder: (context, state) => const LocalReaderScreen(),
|
||||
pageBuilder: (context, state) => CustomTransition(
|
||||
key: state.pageKey,
|
||||
child: const UpdatesScreen(),
|
||||
child: const LocalReaderScreen(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
|
|
@ -319,6 +321,25 @@ class AsyncRouterNotifier extends ChangeNotifier {
|
|||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: "/localReaderReaderView",
|
||||
name: "localReaderReaderView",
|
||||
builder: (context, state) {
|
||||
final localArchive = state.extra as LocalArchive;
|
||||
return LocalReaderReaderView(
|
||||
localArchive: localArchive,
|
||||
);
|
||||
},
|
||||
pageBuilder: (context, state) {
|
||||
final localArchive = state.extra as LocalArchive;
|
||||
return CustomTransition(
|
||||
key: state.pageKey,
|
||||
child: LocalReaderReaderView(
|
||||
localArchive: localArchive,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'cookie_providers.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$cookieStateHash() => r'1ae831fd859a7f084f26be985ebf1e126b38a788';
|
||||
String _$cookieStateHash() => r'73fbf2fed21118db48d07ae0bdd213e1d4789fbd';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'headers.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$headersHash() => r'371ee77f40f010b4e2d5e354c30e666754b34291';
|
||||
String _$headersHash() => r'b25fd0415020bf0585f8ecad168689935188bfad';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
322
lib/utils/test.dart
Normal file
322
lib/utils/test.dart
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/utils/media_query.dart';
|
||||
|
||||
class ImageFinderPage extends StatefulWidget {
|
||||
const ImageFinderPage({super.key});
|
||||
|
||||
@override
|
||||
State createState() => _ImageFinderPageState();
|
||||
}
|
||||
|
||||
class LocalArchive {
|
||||
String? name;
|
||||
|
||||
Uint8List? coverImage;
|
||||
|
||||
List<LocalImage>? images = [];
|
||||
|
||||
LocalExtensionType? extensionType;
|
||||
}
|
||||
|
||||
enum LocalExtensionType { cbz, zip, rar, cbt, tar }
|
||||
|
||||
class LocalImage {
|
||||
String? name;
|
||||
Uint8List? image;
|
||||
}
|
||||
|
||||
class _ImageFinderPageState extends State<ImageFinderPage> {
|
||||
List<LocalArchive> imagePaths = [];
|
||||
bool isLoading = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Image Finder'),
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
//File
|
||||
FilePickerResult? result =
|
||||
await FilePicker.platform.pickFiles();
|
||||
if (result != null) {
|
||||
//File
|
||||
final ddd = await ArchiveSS()
|
||||
.getArchiveDatas(result.files.first.path!);
|
||||
setState(() {
|
||||
imagePaths.add(ddd);
|
||||
isLoading = false;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: const Text("File ")),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
//Directory
|
||||
String? result =
|
||||
await FilePicker.platform.getDirectoryPath();
|
||||
|
||||
if (result != null) {
|
||||
//Directory
|
||||
final ddd = await ArchiveSS().directory(result);
|
||||
setState(() {
|
||||
imagePaths = ddd;
|
||||
isLoading = false;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: const Text("Directory")),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
childAspectRatio: 0.68, crossAxisCount: 3),
|
||||
itemCount: imagePaths.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
child: InkWell(
|
||||
onTap: () {},
|
||||
child: Ink.image(
|
||||
height: 200,
|
||||
fit: BoxFit.cover,
|
||||
image: MemoryImage(imagePaths[index].coverImage!),
|
||||
child: Container(
|
||||
height: 70,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
Colors.black.withOpacity(0.6)
|
||||
],
|
||||
stops: const [0, 1],
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
imagePaths[index].name!,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white,
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(0.5, 0.9),
|
||||
blurRadius: 3.0)
|
||||
],
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius:
|
||||
BorderRadius.circular(5)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2),
|
||||
child: Text(
|
||||
getTypeExtension(imagePaths[index]
|
||||
.extensionType!)
|
||||
.toUpperCase(),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 10),
|
||||
),
|
||||
)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isLoading)
|
||||
Container(
|
||||
width: mediaWidth(context, 1),
|
||||
height: mediaHeight(context, 1),
|
||||
color: Colors.black45,
|
||||
child: UnconstrainedBox(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
height: 200,
|
||||
width: 200,
|
||||
child: const Center(child: ProgressCenter())),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ArchiveSS {
|
||||
Future<List<LocalArchive>> directory(String path) async {
|
||||
return compute(extract, path);
|
||||
}
|
||||
|
||||
Future<LocalArchive> getArchiveDatas(String path) async {
|
||||
return compute(extractArchive, path);
|
||||
}
|
||||
|
||||
List<LocalArchive> extract(String data) {
|
||||
return searchForArchive(Directory(data));
|
||||
}
|
||||
|
||||
List<LocalArchive> list = [];
|
||||
List<LocalArchive> searchForArchive(Directory dir) {
|
||||
List<FileSystemEntity> entities = dir.listSync();
|
||||
for (FileSystemEntity entity in entities) {
|
||||
if (entity is Directory) {
|
||||
searchForArchive(entity);
|
||||
} else if (entity is File) {
|
||||
String path = entity.path;
|
||||
if (isArchiveFile(path)) {
|
||||
final dd = extractArchive(path);
|
||||
list.add(dd);
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
bool isImageFile(String path) {
|
||||
List<String> imageExtensions = ['.png', '.jpg', '.jpeg'];
|
||||
String extension = path.toLowerCase();
|
||||
for (String imageExtension in imageExtensions) {
|
||||
if (extension.endsWith(imageExtension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isArchiveFile(String path) {
|
||||
List<String> imageExtensions = ['.cbz', '.zip', '.cbr', 'cbt', 'tar'];
|
||||
String extension = path.toLowerCase();
|
||||
for (String imageExtension in imageExtensions) {
|
||||
if (extension.endsWith(imageExtension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
LocalArchive extractArchive(String path) {
|
||||
log(path.split('/').last.split("\\").last.split(".").last);
|
||||
final bytes = File(path).readAsBytesSync();
|
||||
final localArchive = LocalArchive()
|
||||
..extensionType = setTypeExtension(
|
||||
path.split('/').last.split("\\").last.split(".").last)
|
||||
..name = path
|
||||
.split('/')
|
||||
.last
|
||||
.split("\\")
|
||||
.last
|
||||
.replaceAll(RegExp(r'\.(cbz|zip|cbt|tar)'), '');
|
||||
Archive? archive;
|
||||
final extensionType = localArchive.extensionType;
|
||||
if (extensionType == LocalExtensionType.cbt ||
|
||||
extensionType == LocalExtensionType.tar) {
|
||||
archive = TarDecoder().decodeBytes(bytes);
|
||||
} else {
|
||||
archive = ZipDecoder().decodeBytes(bytes);
|
||||
}
|
||||
|
||||
for (final file in archive) {
|
||||
final filename = file.name;
|
||||
if (file.isFile) {
|
||||
if (isImageFile(filename)) {
|
||||
if (filename.contains("cover")) {
|
||||
final data = file.content as Uint8List;
|
||||
localArchive.coverImage = Uint8List.fromList(data);
|
||||
} else {
|
||||
final data = file.content as Uint8List;
|
||||
localArchive.images!.add(LocalImage()
|
||||
..image = Uint8List.fromList(data)
|
||||
..name = filename.split('/').last.split("\\").last);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
localArchive.images!.sort((a, b) => a.name!.compareTo(b.name!));
|
||||
localArchive.coverImage ??= localArchive.images!.first.image;
|
||||
return localArchive;
|
||||
}
|
||||
}
|
||||
|
||||
String getTypeExtension(LocalExtensionType type) {
|
||||
return switch (type) {
|
||||
LocalExtensionType.cbt => type.name,
|
||||
LocalExtensionType.zip => type.name,
|
||||
LocalExtensionType.rar => type.name,
|
||||
LocalExtensionType.tar => type.name,
|
||||
_ => type.name,
|
||||
};
|
||||
}
|
||||
|
||||
LocalExtensionType setTypeExtension(String extension) {
|
||||
return switch (extension) {
|
||||
"cbt" => LocalExtensionType.cbt,
|
||||
"zip" => LocalExtensionType.zip,
|
||||
"rar" => LocalExtensionType.rar,
|
||||
"tar" => LocalExtensionType.tar,
|
||||
_ => LocalExtensionType.cbz,
|
||||
};
|
||||
}
|
||||
64
pubspec.lock
64
pubspec.lock
|
|
@ -5,18 +5,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: "98d1d33ed129b372846e862de23a0fc365745f4d7b5e786ce667fcbbb7ac5c07"
|
||||
sha256: "405666cd3cf0ee0a48d21ec67e65406aad2c726d9fa58840d3375e7bdcd32a07"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "55.0.0"
|
||||
version: "60.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "881348aed9b0b425882c97732629a6a31093c8ff20fc4b3b03fb9d3d50a3a126"
|
||||
sha256: "1952250bd005bacb895a01bf1b4dc00e3ba1c526cf47dca54dfe24979c65f5b3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.7.1"
|
||||
version: "5.12.0"
|
||||
analyzer_plugin:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -26,7 +26,7 @@ packages:
|
|||
source: hosted
|
||||
version: "0.11.2"
|
||||
archive:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: archive
|
||||
sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
|
||||
|
|
@ -254,18 +254,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: custom_lint
|
||||
sha256: e87176016465263daf10c209df1f50a52e9e46e7612fab7462da1e6d984638f6
|
||||
sha256: "3ce36c04d30c60cde295588c6185b3f9800e6c18f6670a7ffdb3d5eab39bb942"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.4"
|
||||
version: "0.4.0"
|
||||
custom_lint_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: custom_lint_core
|
||||
sha256: f42f688bc26bdf4c081e011ba27a00439f17c20d9aeca4312f8022e577f8363f
|
||||
sha256: "9170d9db2daf774aa2251a3bc98e4ba903c7702ab07aa438bc83bd3c9a0de57f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.4"
|
||||
version: "0.4.0"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -362,6 +362,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
file_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.3.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -452,14 +460,22 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.15"
|
||||
flutter_riverpod:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_riverpod
|
||||
sha256: "9692634c2c00d2a1a5e96bbde0b79a7c6f0f266aa266d76cd52841f791949a89"
|
||||
sha256: b83ac5827baadefd331ea1d85110f34645827ea234ccabf53a655f41901a9bf4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.5"
|
||||
version: "2.3.6"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
|
@ -706,10 +722,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: cbff87676c352d97116af6dbea05aa28c4d65eb0f6d5677a520c11a69ca9a24d
|
||||
sha256: ceb027f6bc6a60674a233b4a90a7658af1aebdea833da0b5b53c1e9821a78c7b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "4.0.2"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -727,13 +743,13 @@ packages:
|
|||
source: hosted
|
||||
version: "1.8.3"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: c7edf82217d4b2952b2129a61d3ad60f1075b9299e629e149a8d2e39c2e6aad4
|
||||
sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.14"
|
||||
version: "2.0.15"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -906,34 +922,34 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: riverpod
|
||||
sha256: ec5641067d111681ef825754d1327565c987985c7cb52e09bc867b78248854b2
|
||||
sha256: "80e48bebc83010d5e67a11c9514af6b44bbac1ec77b4333c8ea65dbc79e2d8ef"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.5"
|
||||
version: "2.3.6"
|
||||
riverpod_analyzer_utils:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod_analyzer_utils
|
||||
sha256: "7c2d4de69ba06107c3d7f1b3f43dc3fbdcb2f666b480560af654b4eb89af0d6d"
|
||||
sha256: "1b2632a6fc0b659c923a4dcc7cd5da42476f5b3294c70c86c971e63bdd443384"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "0.3.1"
|
||||
riverpod_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: riverpod_annotation
|
||||
sha256: c0f51b3fc5a0cefcbcddb35a10ad542d6c38919c081a25279045158ac7955cfb
|
||||
sha256: cedd6a54b6f5764ffd5c05df57b6676bfc8c01978e14ee60a2c16891038820fe
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.1.1"
|
||||
riverpod_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: riverpod_generator
|
||||
sha256: "2c08a6fbbe80d489f1c5208e5358bfdd4d612f1777c47fdb9ff91bb7a2670529"
|
||||
sha256: cd0595de57ccf5d944ff4b0f68289e11ac6a2eff1e3dfd1d884a43f6f3bcee5e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.2.3"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ dependencies:
|
|||
intl: ^0.18.0
|
||||
google_fonts: ^4.0.3
|
||||
url_launcher: ^6.1.10
|
||||
package_info_plus: ^3.0.2
|
||||
package_info_plus: ^4.0.2
|
||||
background_downloader:
|
||||
git:
|
||||
url: https://github.com/kodjodevf/background_downloader.git
|
||||
|
|
@ -62,6 +62,9 @@ dependencies:
|
|||
share_plus: ^7.0.0
|
||||
xpath_selector_html_parser: ^3.0.1
|
||||
desktop_webview_window: ^0.2.0
|
||||
archive: ^3.3.7
|
||||
file_picker: ^5.3.0
|
||||
path_provider: ^2.0.15
|
||||
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
|
|
@ -72,7 +75,7 @@ dev_dependencies:
|
|||
flutter_test:
|
||||
sdk: flutter
|
||||
build_runner: ^2.3.3
|
||||
riverpod_generator: ^2.1.4
|
||||
riverpod_generator: ^2.2.3
|
||||
flutter_launcher_icons: ^0.13.1
|
||||
isar_generator: 3.1.0+1
|
||||
flutter_lints: ^2.0.1
|
||||
|
|
|
|||
Loading…
Reference in a new issue