ente/lib/ui/detail_page.dart

411 lines
12 KiB
Dart
Raw Normal View History

2021-01-13 10:34:23 +00:00
import 'dart:io';
2020-04-25 22:57:43 +00:00
import 'package:flutter/cupertino.dart';
2020-03-24 19:59:36 +00:00
import 'package:flutter/material.dart';
2020-05-06 16:43:03 +00:00
import 'package:like_button/like_button.dart';
2021-01-13 09:27:41 +00:00
import 'package:photo_manager/photo_manager.dart';
2020-10-03 17:56:18 +00:00
import 'package:photos/services/favorites_service.dart';
2020-06-19 23:03:26 +00:00
import 'package:photos/models/file_type.dart';
import 'package:photos/models/file.dart';
import 'package:photos/ui/video_widget.dart';
import 'package:photos/ui/zoomable_image.dart';
2020-06-13 18:47:48 +00:00
import 'package:photos/utils/date_time_util.dart';
2020-10-23 15:20:51 +00:00
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/file_util.dart';
2020-05-01 18:20:12 +00:00
import 'package:photos/utils/share_util.dart';
2020-05-02 16:28:54 +00:00
import 'package:logging/logging.dart';
2020-10-23 16:18:38 +00:00
import 'package:photos/utils/toast_util.dart';
2020-03-24 19:59:36 +00:00
2020-04-17 08:17:37 +00:00
class DetailPage extends StatefulWidget {
2020-06-19 23:03:26 +00:00
final List<File> files;
final int selectedIndex;
final String tagPrefix;
2020-03-24 19:59:36 +00:00
DetailPage(this.files, this.selectedIndex, this.tagPrefix, {key})
: super(key: key);
2020-04-17 08:17:37 +00:00
@override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
2020-05-06 16:43:03 +00:00
final _logger = Logger("DetailPageState");
2020-04-17 08:17:37 +00:00
bool _shouldDisableScroll = false;
2020-06-19 23:03:26 +00:00
List<File> _files;
PageController _pageController;
int _selectedIndex = 0;
bool _hasPageChanged = false;
2020-04-23 20:00:20 +00:00
2020-03-24 19:59:36 +00:00
@override
2020-04-25 22:57:43 +00:00
void initState() {
2020-06-19 23:03:26 +00:00
_files = widget.files;
_selectedIndex = widget.selectedIndex;
2020-04-25 22:57:43 +00:00
super.initState();
}
2020-04-23 20:00:20 +00:00
2020-04-25 22:57:43 +00:00
@override
Widget build(BuildContext context) {
2020-05-06 16:43:03 +00:00
_logger.info("Opening " +
2020-06-19 23:03:26 +00:00
_files[_selectedIndex].toString() +
2020-04-27 15:28:03 +00:00
". " +
2020-04-25 22:57:43 +00:00
_selectedIndex.toString() +
" / " +
2020-06-19 23:03:26 +00:00
_files.length.toString() +
" files .");
2020-03-24 19:59:36 +00:00
return Scaffold(
2020-04-23 20:00:20 +00:00
appBar: _buildAppBar(),
2020-06-20 21:37:44 +00:00
extendBodyBehindAppBar: true,
2020-03-24 19:59:36 +00:00
body: Center(
child: Container(
2020-04-25 22:57:43 +00:00
child: _buildPageView(),
2020-03-24 19:59:36 +00:00
),
),
backgroundColor: Colors.black,
2020-03-24 19:59:36 +00:00
);
}
2020-04-27 11:57:40 +00:00
Widget _buildPageView() {
_pageController = PageController(initialPage: _selectedIndex);
2020-06-17 15:09:47 +00:00
return PageView.builder(
2020-04-25 22:57:43 +00:00
itemBuilder: (context, index) {
2020-06-19 23:03:26 +00:00
final file = _files[index];
Widget content;
if (file.fileType == FileType.image) {
content = ZoomableImage(
file,
shouldDisableScroll: (value) {
setState(() {
_shouldDisableScroll = value;
});
},
tagPrefix: widget.tagPrefix,
2020-06-19 23:03:26 +00:00
);
} else if (file.fileType == FileType.video) {
content = VideoWidget(
file,
autoPlay: !_hasPageChanged, // Autoplay if it was opened directly
tagPrefix: widget.tagPrefix,
);
2020-06-19 23:03:26 +00:00
} else {
content = Icon(Icons.error);
}
_preloadFiles(index);
return content;
2020-04-25 22:57:43 +00:00
},
2020-06-17 15:09:47 +00:00
onPageChanged: (index) {
setState(() {
_selectedIndex = index;
_hasPageChanged = true;
});
2020-06-19 23:03:26 +00:00
_preloadFiles(index);
2020-04-25 22:57:43 +00:00
},
physics: _shouldDisableScroll
? NeverScrollableScrollPhysics()
: PageScrollPhysics(),
controller: _pageController,
2020-06-19 23:03:26 +00:00
itemCount: _files.length,
2020-04-25 22:57:43 +00:00
);
}
2020-06-19 23:03:26 +00:00
void _preloadFiles(int index) {
2020-06-17 15:09:47 +00:00
if (index > 0) {
2020-07-29 15:48:13 +00:00
preloadFile(_files[index - 1]);
2020-06-17 15:09:47 +00:00
}
2020-06-19 23:03:26 +00:00
if (index < _files.length - 1) {
2020-07-29 15:48:13 +00:00
preloadFile(_files[index + 1]);
2020-06-17 15:09:47 +00:00
}
}
2020-04-23 20:00:20 +00:00
AppBar _buildAppBar() {
final actions = List<Widget>();
actions.add(_getFavoriteButton());
2020-06-13 18:47:48 +00:00
actions.add(PopupMenuButton(
itemBuilder: (context) {
return [
PopupMenuItem(
value: 1,
child: Row(
children: [
2021-01-13 10:34:23 +00:00
Icon(Platform.isAndroid
? Icons.share_outlined
: CupertinoIcons.share),
2020-06-13 18:47:48 +00:00
Padding(
padding: EdgeInsets.all(8),
),
2021-01-08 17:11:32 +00:00
Text("share"),
2020-06-13 18:47:48 +00:00
],
),
),
2020-11-01 06:34:38 +00:00
PopupMenuItem(
value: 2,
child: Row(
children: [
2021-01-13 10:34:23 +00:00
Icon(Platform.isAndroid
? Icons.info_outline
: CupertinoIcons.info),
2020-11-01 06:34:38 +00:00
Padding(
padding: EdgeInsets.all(8),
),
2021-01-08 17:11:32 +00:00
Text("info"),
2020-11-01 06:34:38 +00:00
],
),
),
2020-08-08 15:46:24 +00:00
PopupMenuItem(
value: 3,
child: Row(
children: [
2021-01-13 10:34:23 +00:00
Icon(Platform.isAndroid
? Icons.delete_outline
: CupertinoIcons.delete),
2020-08-08 15:46:24 +00:00
Padding(
padding: EdgeInsets.all(8),
),
2021-01-08 17:11:32 +00:00
Text("delete"),
2020-08-08 15:46:24 +00:00
],
),
2020-06-13 18:47:48 +00:00
)
];
},
onSelected: (value) {
if (value == 1) {
2021-01-13 10:16:31 +00:00
share(context, [_files[_selectedIndex]]);
2020-06-13 18:47:48 +00:00
} else if (value == 2) {
2020-06-19 23:03:26 +00:00
_displayInfo(_files[_selectedIndex]);
2020-08-08 15:46:24 +00:00
} else if (value == 3) {
_showDeleteSheet();
2020-06-13 18:47:48 +00:00
}
},
));
2020-04-23 20:00:20 +00:00
return AppBar(
actions: actions,
2020-06-20 21:37:44 +00:00
backgroundColor: Color(0x00000000),
elevation: 0,
2020-04-23 20:00:20 +00:00
);
}
2020-05-06 16:43:03 +00:00
Widget _getFavoriteButton() {
2020-06-19 23:03:26 +00:00
final file = _files[_selectedIndex];
2020-10-30 20:17:06 +00:00
return FutureBuilder(
future: FavoritesService.instance.isFavorite(file),
builder: (context, snapshot) {
if (snapshot.hasData) {
return _getLikeButton(file, snapshot.data);
} else {
return _getLikeButton(file, false);
}
},
);
}
Widget _getLikeButton(File file, bool isLiked) {
2020-05-06 16:43:03 +00:00
return LikeButton(
2020-10-30 20:17:06 +00:00
isLiked: isLiked,
2020-10-23 15:20:51 +00:00
onTap: (oldValue) async {
2020-10-23 16:18:38 +00:00
final isLiked = !oldValue;
2020-10-24 10:25:02 +00:00
bool hasError = false;
2020-10-23 16:18:38 +00:00
if (isLiked) {
2020-12-03 22:30:10 +00:00
final shouldBlockUser = file.uploadedFileID == null;
var dialog;
if (shouldBlockUser) {
2021-01-08 17:02:41 +00:00
dialog = createProgressDialog(context, "adding to favorites...");
2020-12-03 22:30:10 +00:00
await dialog.show();
}
2020-10-23 16:18:38 +00:00
try {
await FavoritesService.instance.addToFavorites(file);
} catch (e, s) {
_logger.severe(e, s);
2020-10-24 10:25:02 +00:00
hasError = true;
2021-01-08 17:11:32 +00:00
showToast("sorry, could not add this to favorites!");
2020-10-23 16:18:38 +00:00
} finally {
2020-12-03 22:30:10 +00:00
if (shouldBlockUser) {
await dialog.hide();
}
2020-10-23 16:18:38 +00:00
}
} else {
try {
await FavoritesService.instance.removeFromFavorites(file);
} catch (e, s) {
_logger.severe(e, s);
2020-10-24 10:25:02 +00:00
hasError = true;
2021-01-08 17:11:32 +00:00
showToast("sorry, could not remove this from favorites!");
2020-10-23 16:18:38 +00:00
}
2020-10-23 15:20:51 +00:00
}
2020-10-24 10:25:02 +00:00
return hasError ? oldValue : isLiked;
2020-05-06 16:43:03 +00:00
},
2020-06-20 21:37:44 +00:00
likeBuilder: (isLiked) {
return Icon(
Icons.favorite_border,
color: isLiked ? Colors.pinkAccent : Colors.white,
size: 30,
);
},
2020-05-06 16:43:03 +00:00
);
}
2020-06-13 18:47:48 +00:00
2020-06-19 23:03:26 +00:00
Future<void> _displayInfo(File file) async {
2021-01-13 09:27:41 +00:00
AssetEntity asset;
2021-01-13 09:36:28 +00:00
int fileSize;
2020-11-20 07:04:03 +00:00
final isLocalFile = file.localID != null;
if (isLocalFile) {
asset = await file.getAsset();
2021-01-13 09:50:43 +00:00
fileSize = await (await asset.originFile).length();
2020-11-20 07:04:03 +00:00
}
2020-06-13 18:47:48 +00:00
return showDialog<void>(
context: context,
builder: (BuildContext context) {
2020-06-19 23:03:26 +00:00
var items = <Widget>[
Row(
children: [
2021-01-13 09:36:28 +00:00
Icon(Icons.calendar_today_outlined),
2020-06-19 23:03:26 +00:00
Padding(padding: EdgeInsets.all(4)),
Text(getFormattedTime(
DateTime.fromMicrosecondsSinceEpoch(file.creationTime))),
2020-06-19 23:03:26 +00:00
],
),
Padding(padding: EdgeInsets.all(4)),
Row(
children: [
2021-01-13 09:36:28 +00:00
Icon(Icons.folder_outlined),
2020-06-19 23:03:26 +00:00
Padding(padding: EdgeInsets.all(4)),
Text(file.deviceFolder),
],
),
Padding(padding: EdgeInsets.all(4)),
];
2020-11-20 07:04:03 +00:00
if (isLocalFile) {
2021-01-13 09:36:28 +00:00
items.add(Row(
children: [
Icon(Icons.sd_storage_outlined),
Padding(padding: EdgeInsets.all(4)),
Text((fileSize / (1024 * 1024)).toStringAsFixed(2) + " MB"),
],
));
items.add(
Padding(padding: EdgeInsets.all(4)),
);
2020-11-01 06:34:38 +00:00
if (file.fileType == FileType.image) {
items.add(Row(
children: [
2021-01-13 09:36:28 +00:00
Icon(Icons.photo_size_select_actual_outlined),
2020-11-01 06:34:38 +00:00
Padding(padding: EdgeInsets.all(4)),
Text(asset.width.toString() + " x " + asset.height.toString()),
],
));
} else {
items.add(Row(
children: [
2021-01-13 09:36:28 +00:00
Icon(Icons.timer_outlined),
2020-11-01 06:34:38 +00:00
Padding(padding: EdgeInsets.all(4)),
2021-01-13 09:27:41 +00:00
Text(asset.videoDuration.toString().split(".")[0]),
2020-11-01 06:34:38 +00:00
],
));
}
2021-02-14 09:35:06 +00:00
items.add(
Padding(padding: EdgeInsets.all(4)),
);
2020-06-19 23:03:26 +00:00
}
if (file.uploadedFileID != null) {
items.add(Row(
children: [
2021-01-13 09:36:28 +00:00
Icon(Icons.cloud_upload_outlined),
Padding(padding: EdgeInsets.all(4)),
Text(getFormattedTime(
DateTime.fromMicrosecondsSinceEpoch(file.updationTime))),
],
));
}
2020-06-13 18:47:48 +00:00
return AlertDialog(
2020-06-19 23:03:26 +00:00
title: Text(file.title),
2020-06-13 18:47:48 +00:00
content: SingleChildScrollView(
child: ListBody(
2020-06-19 23:03:26 +00:00
children: items,
2020-06-13 18:47:48 +00:00
),
),
actions: <Widget>[
FlatButton(
2021-01-08 17:11:32 +00:00
child: Text('ok'),
2020-06-13 18:47:48 +00:00
onPressed: () {
2021-03-21 11:21:45 +00:00
Navigator.of(context, rootNavigator: true).pop('dialog');
2020-06-13 18:47:48 +00:00
},
),
],
);
},
);
}
void _showDeleteSheet() {
final fileToBeDeleted = _files[_selectedIndex];
final actions = List<Widget>();
if (fileToBeDeleted.uploadedFileID == null) {
actions.add(CupertinoActionSheetAction(
2021-01-08 17:11:32 +00:00
child: Text("everywhere"),
isDestructiveAction: true,
onPressed: () async {
await deleteFilesFromEverywhere(context, [fileToBeDeleted]);
_onFileDeleted();
},
));
} else {
if (fileToBeDeleted.localID != null) {
actions.add(CupertinoActionSheetAction(
2021-01-08 17:11:32 +00:00
child: Text("on this device"),
isDestructiveAction: true,
onPressed: () async {
await deleteFilesOnDeviceOnly(context, [fileToBeDeleted]);
2021-01-08 17:11:32 +00:00
showToast("file deleted from device");
Navigator.of(context, rootNavigator: true).pop();
},
));
}
actions.add(CupertinoActionSheetAction(
2021-01-08 17:11:32 +00:00
child: Text("everywhere"),
isDestructiveAction: true,
onPressed: () async {
await deleteFilesFromEverywhere(context, [fileToBeDeleted]);
_onFileDeleted();
},
));
}
final action = CupertinoActionSheet(
2021-01-08 17:11:32 +00:00
title: Text("delete file?"),
actions: actions,
cancelButton: CupertinoActionSheetAction(
2021-01-08 17:11:32 +00:00
child: Text("cancel"),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
},
),
);
showCupertinoModalPopup(context: context, builder: (_) => action);
}
Future _onFileDeleted() async {
final file = _files[_selectedIndex];
final totalFiles = _files.length;
if (totalFiles == 1) {
// Deleted the only file
Navigator.of(context, rootNavigator: true).pop(); // Close pageview
Navigator.of(context, rootNavigator: true).pop(); // Close gallery
return;
}
if (_selectedIndex == totalFiles - 1) {
// Deleted the last file
await _pageController.previousPage(
duration: Duration(milliseconds: 200), curve: Curves.easeInOut);
setState(() {
_files.remove(file);
});
} else {
await _pageController.nextPage(
duration: Duration(milliseconds: 200), curve: Curves.easeInOut);
setState(() {
_selectedIndex--;
_files.remove(file);
});
}
Navigator.of(context, rootNavigator: true).pop(); // Close dialog
}
2020-04-23 20:00:20 +00:00
}