File-Drive-App-Frontend/lib/screens/gallery_screen.dart

269 lines
8.7 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:photo_manager_image_provider/photo_manager_image_provider.dart';
import 'package:provider/provider.dart';
import '../controllers/upload_controller.dart';
import '../core/app_theme.dart';
class GalleryScreen extends StatefulWidget {
const GalleryScreen({super.key, required this.folderId});
final String? folderId;
@override
State<GalleryScreen> createState() => _GalleryScreenState();
}
class _GalleryScreenState extends State<GalleryScreen> {
final _scroll = ScrollController();
final List<AssetEntity> _assets = [];
final Set<String> _selected = {};
AssetPathEntity? _album;
int _page = 0;
bool _loading = true;
bool _more = true;
bool _queueing = false;
String? _error;
@override
void initState() {
super.initState();
_scroll.addListener(() {
if (_scroll.position.extentAfter < 800) _loadMore();
});
_initialize();
}
@override
void dispose() {
_scroll.dispose();
super.dispose();
}
Future<void> _initialize() async {
final permission = await PhotoManager.requestPermissionExtend();
if (!permission.hasAccess) {
setState(() {
_loading = false;
_error = 'Gallery access is required to choose your media.';
});
return;
}
final albums = await PhotoManager.getAssetPathList(
type: RequestType.common,
onlyAll: true,
);
if (albums.isEmpty) {
setState(() {
_loading = false;
_error = 'No photos or videos were found.';
});
return;
}
_album = albums.first;
await _loadMore();
}
Future<void> _loadMore() async {
if (!_more ||
(_loading && _assets.isNotEmpty) ||
_album == null && !_loading) {
return;
}
final album = _album;
if (album == null) return;
if (_assets.isNotEmpty) setState(() => _loading = true);
final page = await album.getAssetListPaged(page: _page, size: 80);
if (!mounted) return;
setState(() {
_assets.addAll(page);
_page++;
_more = page.length == 80;
_loading = false;
});
}
Future<void> _queueUploads() async {
if (_selected.isEmpty) return;
setState(() => _queueing = true);
var queued = 0;
try {
final support = await getApplicationSupportDirectory();
final queue = Directory(
'${support.path}${Platform.pathSeparator}upload_queue',
);
await queue.create(recursive: true);
final chosen = _assets
.where((asset) => _selected.contains(asset.id))
.toList();
for (var index = 0; index < chosen.length; index++) {
final asset = chosen[index];
final source = await asset.originFile;
if (source == null) continue;
final original = (asset.title ?? source.uri.pathSegments.last)
.replaceAll(RegExp(r'[\\/:*?"<>|]'), '_');
final stagedName =
'${DateTime.now().microsecondsSinceEpoch}_$index-$original';
await source.copy('${queue.path}${Platform.pathSeparator}$stagedName');
if (!mounted) return;
await context.read<UploadController>().enqueue(
filename: stagedName,
displayName: original,
folderId: widget.folderId,
);
queued++;
}
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'$queued file${queued == 1 ? '' : 's'} added to the background upload queue.',
),
),
);
Navigator.pop(context, true);
}
} catch (error) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Could not queue all files: $error')),
);
}
} finally {
if (mounted) setState(() => _queueing = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Choose from gallery'),
actions: [
if (_selected.isNotEmpty)
TextButton(
onPressed: () => setState(_selected.clear),
child: const Text('Clear'),
),
],
),
body: _error != null
? Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.photo_library_outlined,
size: 62,
color: AppTheme.blue,
),
const SizedBox(height: 14),
Text(_error!, textAlign: TextAlign.center),
const SizedBox(height: 14),
OutlinedButton(
onPressed: PhotoManager.openSetting,
child: const Text('Open settings'),
),
],
),
),
)
: _assets.isEmpty && _loading
? const Center(child: CircularProgressIndicator())
: GridView.builder(
controller: _scroll,
padding: const EdgeInsets.fromLTRB(3, 3, 3, 110),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 3,
crossAxisSpacing: 3,
),
itemCount: _assets.length + (_loading ? 1 : 0),
itemBuilder: (context, index) {
if (index >= _assets.length) {
return const Center(
child: CircularProgressIndicator(strokeWidth: 2),
);
}
final asset = _assets[index];
final selected = _selected.contains(asset.id);
return GestureDetector(
onTap: () => setState(
() => selected
? _selected.remove(asset.id)
: _selected.add(asset.id),
),
child: Stack(
fit: StackFit.expand,
children: [
AssetEntityImage(
asset,
isOriginal: false,
thumbnailSize: const ThumbnailSize.square(350),
fit: BoxFit.cover,
),
if (selected)
Container(color: AppTheme.blue.withValues(alpha: .30)),
if (asset.type == AssetType.video)
const Positioned(
left: 7,
bottom: 7,
child: Icon(
Icons.videocam_rounded,
color: Colors.white,
),
),
Positioned(
right: 7,
top: 7,
child: AnimatedContainer(
duration: const Duration(milliseconds: 150),
width: 25,
height: 25,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: selected ? AppTheme.blue : Colors.black38,
border: Border.all(color: Colors.white, width: 2),
),
child: selected
? const Icon(
Icons.check,
color: Colors.white,
size: 17,
)
: null,
),
),
],
),
);
},
),
bottomNavigationBar: SafeArea(
child: Padding(
padding: const EdgeInsets.all(14),
child: FilledButton.icon(
onPressed: _selected.isEmpty || _queueing ? null : _queueUploads,
icon: _queueing
? const SizedBox.square(
dimension: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Icon(Icons.cloud_upload_outlined),
label: Text(
_selected.isEmpty
? 'Select photos or videos'
: 'Upload ${_selected.length} selected',
),
),
),
),
);
}
}