ente/lib/ui/gallery.dart

240 lines
6.6 KiB
Dart
Raw Normal View History

2020-06-10 18:17:54 +00:00
import 'dart:async';
import 'dart:collection';
2020-04-12 12:38:49 +00:00
import 'package:flutter/cupertino.dart';
2020-03-28 18:18:27 +00:00
import 'package:flutter/material.dart';
2020-04-18 18:46:38 +00:00
import 'package:flutter/services.dart';
2020-06-17 15:09:47 +00:00
import 'package:logging/logging.dart';
2020-06-15 19:03:43 +00:00
import 'package:photos/events/event.dart';
2020-06-19 23:03:26 +00:00
import 'package:photos/models/file.dart';
2020-05-01 18:20:12 +00:00
import 'package:photos/ui/detail_page.dart';
import 'package:photos/ui/loading_widget.dart';
import 'package:photos/ui/sync_indicator.dart';
2020-05-01 18:20:12 +00:00
import 'package:photos/ui/thumbnail_widget.dart';
import 'package:photos/utils/date_time_util.dart';
2020-06-06 13:48:29 +00:00
import 'package:pull_to_refresh/pull_to_refresh.dart';
2020-03-28 18:18:27 +00:00
class Gallery extends StatefulWidget {
2020-06-19 23:03:26 +00:00
final Future<List<File>> Function() loader;
2020-06-16 12:56:23 +00:00
// TODO: Verify why the event is necessary when calling loader post onRefresh
// should have done the job.
final Stream<Event> reloadEvent;
final Future<void> Function() onRefresh;
2020-06-19 23:03:26 +00:00
final Set<File> selectedFiles;
final Function(Set<File>) onFileSelectionChange;
2020-04-14 15:36:18 +00:00
Gallery(
2020-06-16 12:56:23 +00:00
this.loader, {
this.reloadEvent,
this.onRefresh,
2020-06-19 23:03:26 +00:00
this.selectedFiles,
this.onFileSelectionChange,
});
2020-04-14 15:36:18 +00:00
2020-03-28 18:18:27 +00:00
@override
_GalleryState createState() {
2020-04-18 18:46:38 +00:00
return _GalleryState();
2020-03-28 18:18:27 +00:00
}
}
class _GalleryState extends State<Gallery> {
2020-06-17 15:09:47 +00:00
final Logger _logger = Logger("Gallery");
2020-04-12 12:38:49 +00:00
final ScrollController _scrollController = ScrollController();
2020-06-19 23:03:26 +00:00
final List<List<File>> _collatedFiles = List<List<File>>();
bool _requiresLoad = false;
2020-06-19 23:03:26 +00:00
AsyncSnapshot<List<File>> _lastSnapshot;
Set<File> _selectedFiles = HashSet<File>();
List<File> _files;
RefreshController _refreshController = RefreshController();
2020-06-10 18:17:54 +00:00
@override
void initState() {
_requiresLoad = true;
2020-06-16 12:56:23 +00:00
if (widget.reloadEvent != null) {
widget.reloadEvent.listen((event) {
setState(() {
_requiresLoad = true;
});
2020-06-15 19:03:43 +00:00
});
2020-06-16 12:56:23 +00:00
}
2020-06-10 18:17:54 +00:00
super.initState();
}
2020-03-28 18:18:27 +00:00
@override
Widget build(BuildContext context) {
if (!_requiresLoad) {
return _onSnapshotAvailable(_lastSnapshot);
}
2020-06-19 23:03:26 +00:00
return FutureBuilder<List<File>>(
2020-06-16 12:56:23 +00:00
future: widget.loader(),
builder: (context, snapshot) {
_lastSnapshot = snapshot;
return _onSnapshotAvailable(snapshot);
},
);
}
2020-06-19 23:03:26 +00:00
Widget _onSnapshotAvailable(AsyncSnapshot<List<File>> snapshot) {
if (snapshot.hasData) {
_requiresLoad = false;
return _onDataLoaded(snapshot.data);
} else if (snapshot.hasError) {
_requiresLoad = false;
return Center(child: Text(snapshot.error.toString()));
} else {
return Center(child: loadWidget);
}
}
2020-06-19 23:03:26 +00:00
Widget _onDataLoaded(List<File> files) {
_files = files;
if (_files.isEmpty) {
2020-06-15 19:38:57 +00:00
return Center(child: Text("Nothing to see here! 👀"));
}
2020-06-19 23:03:26 +00:00
_selectedFiles = widget.selectedFiles ?? Set<File>();
_collateFiles();
final list = ListView.builder(
2020-06-19 23:03:26 +00:00
itemCount: _collatedFiles.length,
itemBuilder: _buildListItem,
controller: _scrollController,
cacheExtent: 1000,
2020-04-14 15:36:18 +00:00
);
2020-06-16 12:56:23 +00:00
if (widget.onRefresh != null) {
return SmartRefresher(
controller: _refreshController,
child: list,
header: SyncIndicator(_refreshController),
onRefresh: () {
2020-06-16 12:56:23 +00:00
widget.onRefresh().then((_) {
_refreshController.refreshCompleted();
2020-06-16 12:56:23 +00:00
widget.loader().then((_) => setState(() {
_requiresLoad = true;
}));
}).catchError((e) {
_refreshController.refreshFailed();
setState(() {});
});
},
);
} else {
return list;
}
2020-03-28 18:18:27 +00:00
}
2020-04-13 15:01:27 +00:00
Widget _buildListItem(BuildContext context, int index) {
2020-06-19 23:03:26 +00:00
var files = _collatedFiles[index];
2020-04-13 15:01:27 +00:00
return Column(
2020-06-19 23:03:26 +00:00
children: <Widget>[_getDay(files[0].createTimestamp), _getGallery(files)],
2020-04-13 15:01:27 +00:00
);
}
Widget _getDay(int timestamp) {
2020-04-13 15:01:27 +00:00
return Container(
padding: const EdgeInsets.all(8.0),
2020-04-13 15:01:27 +00:00
alignment: Alignment.centerLeft,
child: Text(
getDayAndMonth(DateTime.fromMicrosecondsSinceEpoch(timestamp)),
style: TextStyle(fontSize: 16),
),
2020-04-13 15:01:27 +00:00
);
}
2020-06-19 23:03:26 +00:00
Widget _getGallery(List<File> files) {
2020-04-13 15:01:27 +00:00
return GridView.builder(
shrinkWrap: true,
padding: EdgeInsets.only(bottom: 12),
physics: ScrollPhysics(), // to disable GridView's scrolling
itemBuilder: (context, index) {
2020-06-19 23:03:26 +00:00
return _buildFile(context, files[index]);
2020-04-13 15:01:27 +00:00
},
2020-06-19 23:03:26 +00:00
itemCount: files.length,
2020-04-13 15:01:27 +00:00
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
),
);
}
2020-06-19 23:03:26 +00:00
Widget _buildFile(BuildContext context, File file) {
2020-03-28 18:18:27 +00:00
return GestureDetector(
2020-04-05 14:00:44 +00:00
onTap: () {
2020-06-19 23:03:26 +00:00
if (_selectedFiles.isNotEmpty) {
_selectFile(file);
} else {
2020-06-19 23:03:26 +00:00
_routeToDetailPage(file, context);
}
2020-03-30 10:57:04 +00:00
},
onLongPress: () {
2020-04-18 18:46:38 +00:00
HapticFeedback.lightImpact();
2020-06-19 23:03:26 +00:00
_selectFile(file);
2020-03-28 18:18:27 +00:00
},
child: Container(
margin: const EdgeInsets.all(2.0),
decoration: BoxDecoration(
2020-06-19 23:03:26 +00:00
border: _selectedFiles.contains(file)
? Border.all(width: 4.0, color: Colors.blue)
: null,
2020-04-12 12:38:49 +00:00
),
2020-06-17 15:09:47 +00:00
child: Hero(
tag: file.tag(),
2020-06-19 23:03:26 +00:00
child: ThumbnailWidget(file),
2020-06-17 15:09:47 +00:00
),
2020-04-12 12:38:49 +00:00
),
);
}
2020-06-19 23:03:26 +00:00
void _selectFile(File file) {
setState(() {
2020-06-19 23:03:26 +00:00
if (_selectedFiles.contains(file)) {
_selectedFiles.remove(file);
} else {
2020-06-19 23:03:26 +00:00
_selectedFiles.add(file);
}
2020-06-19 23:03:26 +00:00
widget.onFileSelectionChange(_selectedFiles);
});
2020-04-12 12:38:49 +00:00
}
2020-06-19 23:03:26 +00:00
void _routeToDetailPage(File file, BuildContext context) {
2020-04-25 22:57:43 +00:00
final page = DetailPage(
2020-06-19 23:03:26 +00:00
_files,
_files.indexOf(file),
2020-04-25 22:57:43 +00:00
);
2020-03-28 18:18:27 +00:00
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return page;
},
),
);
}
2020-04-17 09:54:42 +00:00
2020-06-19 23:03:26 +00:00
void _collateFiles() {
final dailyFiles = List<File>();
final collatedFiles = List<List<File>>();
for (int index = 0; index < _files.length; index++) {
2020-04-17 09:54:42 +00:00
if (index > 0 &&
2020-06-19 23:03:26 +00:00
!_areFilesFromSameDay(_files[index], _files[index - 1])) {
var collatedDailyFiles = List<File>();
collatedDailyFiles.addAll(dailyFiles);
collatedFiles.add(collatedDailyFiles);
dailyFiles.clear();
2020-04-17 09:54:42 +00:00
}
2020-06-19 23:03:26 +00:00
dailyFiles.add(_files[index]);
2020-04-17 09:54:42 +00:00
}
2020-06-19 23:03:26 +00:00
if (dailyFiles.isNotEmpty) {
collatedFiles.add(dailyFiles);
2020-04-17 09:54:42 +00:00
}
2020-06-19 23:03:26 +00:00
_collatedFiles.clear();
_collatedFiles.addAll(collatedFiles);
2020-04-17 09:54:42 +00:00
}
2020-06-19 23:03:26 +00:00
bool _areFilesFromSameDay(File first, File second) {
var firstDate = DateTime.fromMicrosecondsSinceEpoch(first.createTimestamp);
2020-04-17 09:54:42 +00:00
var secondDate =
2020-06-19 23:03:26 +00:00
DateTime.fromMicrosecondsSinceEpoch(second.createTimestamp);
2020-04-17 09:54:42 +00:00
return firstDate.year == secondDate.year &&
firstDate.month == secondDate.month &&
firstDate.day == secondDate.day;
}
2020-03-28 18:18:27 +00:00
}