Files
Qlyra/lib/core/media/gallery_source.dart
T

163 lines
4.1 KiB
Dart

import 'dart:io';
import 'dart:typed_data';
import 'package:photo_manager/photo_manager.dart';
enum GalleryPermission { granted, limited, denied }
abstract class GalleryItem {
String get id;
bool get isVideo;
Duration? get duration;
File? get localFile;
Future<Uint8List?> thumbnail(int size);
Future<File?> originFile();
}
abstract class GallerySource {
Future<GalleryPermission> ensurePermission();
Future<List<GalleryItem>> load({int limit});
Future<void> openSettings();
Future<void> manageAccess();
factory GallerySource.create() {
if (Platform.isAndroid || Platform.isIOS) {
return _PhotoManagerSource();
}
return _DesktopGallerySource();
}
}
class _PhotoManagerSource implements GallerySource {
@override
Future<GalleryPermission> ensurePermission() async {
final state = await PhotoManager.requestPermissionExtend();
if (state.isAuth) return GalleryPermission.granted;
if (state.hasAccess) return GalleryPermission.limited;
return GalleryPermission.denied;
}
@override
Future<List<GalleryItem>> load({int limit = 120}) async {
final paths = await PhotoManager.getAssetPathList(
type: RequestType.common,
onlyAll: true,
filterOption: FilterOptionGroup(
orders: const [
OrderOption(type: OrderOptionType.createDate, asc: false),
],
),
);
if (paths.isEmpty) return const [];
final assets = await paths.first.getAssetListRange(start: 0, end: limit);
return assets.map((a) => _AssetGalleryItem(a)).toList();
}
@override
Future<void> openSettings() => PhotoManager.openSetting();
@override
Future<void> manageAccess() => PhotoManager.presentLimited();
}
class _AssetGalleryItem implements GalleryItem {
final AssetEntity asset;
_AssetGalleryItem(this.asset);
@override
String get id => asset.id;
@override
bool get isVideo => asset.type == AssetType.video;
@override
Duration? get duration => isVideo ? Duration(seconds: asset.duration) : null;
@override
File? get localFile => null;
@override
Future<Uint8List?> thumbnail(int size) =>
asset.thumbnailDataWithSize(ThumbnailSize.square(size));
@override
Future<File?> originFile() => asset.file;
}
class _DesktopGallerySource implements GallerySource {
static const _imageExtensions = {
'.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.heic', '.heif',
};
@override
Future<GalleryPermission> ensurePermission() async =>
GalleryPermission.granted;
@override
Future<List<GalleryItem>> load({int limit = 120}) async {
final entries = <({File file, DateTime modified})>[];
for (final dir in _candidateDirs()) {
if (!dir.existsSync()) continue;
try {
for (final entity in dir.listSync(followLinks: false)) {
if (entity is! File || !_isImage(entity.path)) continue;
entries.add((file: entity, modified: entity.statSync().modified));
}
} catch (_) {}
}
entries.sort((a, b) => b.modified.compareTo(a.modified));
return entries
.take(limit)
.map((e) => _FileGalleryItem(e.file))
.toList();
}
@override
Future<void> openSettings() async {}
@override
Future<void> manageAccess() async {}
List<Directory> _candidateDirs() {
final home =
Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'];
if (home == null || home.isEmpty) return const [];
return [
Directory('$home/Pictures'),
Directory('$home/Изображения'),
Directory('$home/Images'),
];
}
bool _isImage(String path) {
final dot = path.lastIndexOf('.');
if (dot < 0) return false;
return _imageExtensions.contains(path.substring(dot).toLowerCase());
}
}
class _FileGalleryItem implements GalleryItem {
final File file;
_FileGalleryItem(this.file);
@override
String get id => file.path;
@override
bool get isVideo => false;
@override
Duration? get duration => null;
@override
File? get localFile => file;
@override
Future<Uint8List?> thumbnail(int size) async => null;
@override
Future<File?> originFile() async => file;
}