gallery_app_bar_widget.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:flutter/cupertino.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:logging/logging.dart';
  6. import 'package:page_transition/page_transition.dart';
  7. import 'package:photos/core/configuration.dart';
  8. import 'package:photos/core/event_bus.dart';
  9. import 'package:photos/events/subscription_purchased_event.dart';
  10. import 'package:photos/models/collection.dart';
  11. import 'package:photos/models/selected_files.dart';
  12. import 'package:photos/services/collections_service.dart';
  13. import 'package:photos/ui/create_collection_page.dart';
  14. import 'package:photos/ui/share_collection_widget.dart';
  15. import 'package:photos/utils/delete_file_util.dart';
  16. import 'package:photos/utils/dialog_util.dart';
  17. import 'package:photos/utils/share_util.dart';
  18. import 'package:photos/utils/toast_util.dart';
  19. enum GalleryAppBarType {
  20. homepage,
  21. local_folder,
  22. // indicator for gallery view of collections shared with the user
  23. shared_collection,
  24. owned_collection,
  25. search_results
  26. }
  27. class GalleryAppBarWidget extends StatefulWidget {
  28. final GalleryAppBarType type;
  29. final String title;
  30. final SelectedFiles selectedFiles;
  31. final String path;
  32. final Collection collection;
  33. GalleryAppBarWidget(
  34. this.type,
  35. this.title,
  36. this.selectedFiles, {
  37. this.path,
  38. this.collection,
  39. });
  40. @override
  41. _GalleryAppBarWidgetState createState() => _GalleryAppBarWidgetState();
  42. }
  43. class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
  44. final _logger = Logger("GalleryAppBar");
  45. StreamSubscription _userAuthEventSubscription;
  46. Function() _selectedFilesListener;
  47. @override
  48. void initState() {
  49. _selectedFilesListener = () {
  50. setState(() {});
  51. };
  52. widget.selectedFiles.addListener(_selectedFilesListener);
  53. _userAuthEventSubscription =
  54. Bus.instance.on<SubscriptionPurchasedEvent>().listen((event) {
  55. setState(() {});
  56. });
  57. super.initState();
  58. }
  59. @override
  60. void dispose() {
  61. _userAuthEventSubscription.cancel();
  62. widget.selectedFiles.removeListener(_selectedFilesListener);
  63. super.dispose();
  64. }
  65. @override
  66. Widget build(BuildContext context) {
  67. if (widget.selectedFiles.files.isEmpty) {
  68. return AppBar(
  69. backgroundColor: widget.type == GalleryAppBarType.homepage
  70. ? Color(0x00000000)
  71. : null,
  72. elevation: 0,
  73. title: widget.type == GalleryAppBarType.homepage
  74. ? Container()
  75. : Text(
  76. widget.title,
  77. style: TextStyle(
  78. color: Colors.white.withOpacity(0.80),
  79. ),
  80. ),
  81. actions: _getDefaultActions(context),
  82. );
  83. }
  84. return AppBar(
  85. leading: IconButton(
  86. icon: Icon(Platform.isAndroid ? Icons.clear : CupertinoIcons.clear),
  87. onPressed: () {
  88. _clearSelectedFiles();
  89. },
  90. ),
  91. title: Text(widget.selectedFiles.files.length.toString()),
  92. actions: _getActions(context),
  93. );
  94. }
  95. List<Widget> _getDefaultActions(BuildContext context) {
  96. List<Widget> actions = <Widget>[];
  97. if (Configuration.instance.hasConfiguredAccount() &&
  98. (widget.type == GalleryAppBarType.local_folder ||
  99. widget.type == GalleryAppBarType.owned_collection)) {
  100. actions.add(IconButton(
  101. icon: Icon(Icons.person_add),
  102. onPressed: () {
  103. _showShareCollectionDialog();
  104. },
  105. ));
  106. }
  107. return actions;
  108. }
  109. Future<void> _showShareCollectionDialog() async {
  110. var collection = widget.collection;
  111. final dialog = createProgressDialog(context, "please wait...");
  112. await dialog.show();
  113. if (collection == null) {
  114. if (widget.type == GalleryAppBarType.local_folder) {
  115. collection =
  116. CollectionsService.instance.getCollectionForPath(widget.path);
  117. if (collection == null) {
  118. try {
  119. collection = await CollectionsService.instance
  120. .getOrCreateForPath(widget.path);
  121. } catch (e, s) {
  122. _logger.severe(e, s);
  123. await dialog.hide();
  124. showGenericErrorDialog(context);
  125. }
  126. }
  127. } else {
  128. throw Exception(
  129. "Cannot create a collection of type" + widget.type.toString());
  130. }
  131. } else {
  132. final sharees =
  133. await CollectionsService.instance.getSharees(collection.id);
  134. collection = collection.copyWith(sharees: sharees);
  135. }
  136. await dialog.hide();
  137. return showDialog<void>(
  138. context: context,
  139. builder: (BuildContext context) {
  140. return SharingDialog(collection);
  141. },
  142. );
  143. }
  144. Future<void> _createAlbum() async {
  145. Navigator.push(
  146. context,
  147. PageTransition(
  148. type: PageTransitionType.bottomToTop,
  149. child: CreateCollectionPage(
  150. widget.selectedFiles,
  151. null,
  152. )));
  153. }
  154. Future<void> _moveFiles() async {
  155. Navigator.push(
  156. context,
  157. PageTransition(
  158. type: PageTransitionType.bottomToTop,
  159. child: CreateCollectionPage(
  160. widget.selectedFiles,
  161. null,
  162. actionType: CollectionActionType.moveFiles,
  163. )));
  164. }
  165. List<Widget> _getActions(BuildContext context) {
  166. List<Widget> actions = <Widget>[];
  167. // skip add button for incoming collection till this feature is implemented
  168. if (Configuration.instance.hasConfiguredAccount() &&
  169. widget.type != GalleryAppBarType.shared_collection) {
  170. actions.add(IconButton(
  171. icon:
  172. Icon(Platform.isAndroid ? Icons.add_outlined : CupertinoIcons.add),
  173. onPressed: () {
  174. _createAlbum();
  175. },
  176. ));
  177. }
  178. if (Configuration.instance.hasConfiguredAccount() &&
  179. widget.type == GalleryAppBarType.owned_collection) {
  180. actions.add(IconButton(
  181. icon: Icon(Platform.isAndroid
  182. ? Icons.arrow_right_alt_rounded
  183. : CupertinoIcons.arrow_right),
  184. onPressed: () {
  185. _moveFiles();
  186. },
  187. ));
  188. }
  189. actions.add(IconButton(
  190. icon: Icon(
  191. Platform.isAndroid ? Icons.share_outlined : CupertinoIcons.share),
  192. onPressed: () {
  193. _shareSelected(context);
  194. },
  195. ));
  196. if (widget.type == GalleryAppBarType.homepage ||
  197. widget.type == GalleryAppBarType.local_folder) {
  198. actions.add(IconButton(
  199. icon: Icon(
  200. Platform.isAndroid ? Icons.delete_outline : CupertinoIcons.delete),
  201. onPressed: () {
  202. _showDeleteSheet(context);
  203. },
  204. ));
  205. } else if (widget.type == GalleryAppBarType.owned_collection) {
  206. if (widget.collection.type == CollectionType.folder) {
  207. actions.add(IconButton(
  208. icon: Icon(Platform.isAndroid
  209. ? Icons.delete_outline
  210. : CupertinoIcons.delete),
  211. onPressed: () {
  212. _showDeleteSheet(context);
  213. },
  214. ));
  215. } else {
  216. actions.add(IconButton(
  217. icon: Icon(Icons.remove_circle_outline_rounded),
  218. onPressed: () {
  219. _showRemoveFromCollectionSheet(context);
  220. },
  221. ));
  222. }
  223. }
  224. return actions;
  225. }
  226. void _shareSelected(BuildContext context) {
  227. share(context, widget.selectedFiles.files.toList());
  228. }
  229. void _showRemoveFromCollectionSheet(BuildContext context) {
  230. final count = widget.selectedFiles.files.length;
  231. final action = CupertinoActionSheet(
  232. title: Text("remove " +
  233. count.toString() +
  234. " file" +
  235. (count == 1 ? "" : "s") +
  236. " from " +
  237. widget.collection.name +
  238. "?"),
  239. actions: <Widget>[
  240. CupertinoActionSheetAction(
  241. child: Text("remove"),
  242. isDestructiveAction: true,
  243. onPressed: () async {
  244. Navigator.of(context, rootNavigator: true).pop();
  245. final dialog = createProgressDialog(context, "removing files...");
  246. await dialog.show();
  247. try {
  248. await CollectionsService.instance.removeFromCollection(
  249. widget.collection.id, widget.selectedFiles.files.toList());
  250. await dialog.hide();
  251. widget.selectedFiles.clearAll();
  252. } catch (e, s) {
  253. _logger.severe(e, s);
  254. await dialog.hide();
  255. showGenericErrorDialog(context);
  256. }
  257. },
  258. ),
  259. ],
  260. cancelButton: CupertinoActionSheetAction(
  261. child: Text("cancel"),
  262. onPressed: () {
  263. Navigator.of(context, rootNavigator: true).pop();
  264. },
  265. ),
  266. );
  267. showCupertinoModalPopup(context: context, builder: (_) => action);
  268. }
  269. void _showDeleteSheet(BuildContext context) {
  270. final count = widget.selectedFiles.files.length;
  271. bool containsUploadedFile = false, containsLocalFile = false;
  272. for (final file in widget.selectedFiles.files) {
  273. if (file.uploadedFileID != null) {
  274. containsUploadedFile = true;
  275. }
  276. if (file.localID != null) {
  277. containsLocalFile = true;
  278. }
  279. }
  280. final actions = <Widget>[];
  281. if (containsUploadedFile && containsLocalFile) {
  282. actions.add(CupertinoActionSheetAction(
  283. child: Text("this device"),
  284. isDestructiveAction: true,
  285. onPressed: () async {
  286. Navigator.of(context, rootNavigator: true).pop();
  287. await deleteFilesOnDeviceOnly(
  288. context, widget.selectedFiles.files.toList());
  289. _clearSelectedFiles();
  290. showToast("files deleted from device");
  291. },
  292. ));
  293. actions.add(CupertinoActionSheetAction(
  294. child: Text("everywhere"),
  295. isDestructiveAction: true,
  296. onPressed: () async {
  297. Navigator.of(context, rootNavigator: true).pop();
  298. await deleteFilesFromEverywhere(
  299. context, widget.selectedFiles.files.toList());
  300. _clearSelectedFiles();
  301. },
  302. ));
  303. } else {
  304. actions.add(CupertinoActionSheetAction(
  305. child: Text("delete forever"),
  306. isDestructiveAction: true,
  307. onPressed: () async {
  308. Navigator.of(context, rootNavigator: true).pop();
  309. await deleteFilesFromEverywhere(
  310. context, widget.selectedFiles.files.toList());
  311. _clearSelectedFiles();
  312. },
  313. ));
  314. }
  315. final action = CupertinoActionSheet(
  316. title: Text("delete " +
  317. count.toString() +
  318. " file" +
  319. (count == 1 ? "" : "s") +
  320. (containsUploadedFile && containsLocalFile ? " from" : "?")),
  321. actions: actions,
  322. cancelButton: CupertinoActionSheetAction(
  323. child: Text("cancel"),
  324. onPressed: () {
  325. Navigator.of(context, rootNavigator: true).pop();
  326. },
  327. ),
  328. );
  329. showCupertinoModalPopup(context: context, builder: (_) => action);
  330. }
  331. void _clearSelectedFiles() {
  332. widget.selectedFiles.clearAll();
  333. }
  334. }