gallery_app_bar_widget.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  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. if (widget.type == GalleryAppBarType.homepage) {
  225. actions.add(PopupMenuButton(
  226. itemBuilder: (context) {
  227. final List<PopupMenuItem> items = [];
  228. items.add(
  229. PopupMenuItem(
  230. value: 1,
  231. child: Row(
  232. children: [
  233. Icon(Platform.isAndroid
  234. ? Icons.archive_outlined
  235. : CupertinoIcons.archivebox),
  236. Padding(
  237. padding: EdgeInsets.all(8),
  238. ),
  239. Text("archive"),
  240. ],
  241. ),
  242. ),
  243. );
  244. return items;
  245. },
  246. onSelected: (value) {
  247. if (value == 1) {
  248. showToast("coming soon");
  249. }
  250. },
  251. ));
  252. }
  253. return actions;
  254. }
  255. void _shareSelected(BuildContext context) {
  256. share(context, widget.selectedFiles.files.toList());
  257. }
  258. void _showRemoveFromCollectionSheet(BuildContext context) {
  259. final count = widget.selectedFiles.files.length;
  260. final action = CupertinoActionSheet(
  261. title: Text("remove " +
  262. count.toString() +
  263. " file" +
  264. (count == 1 ? "" : "s") +
  265. " from " +
  266. widget.collection.name +
  267. "?"),
  268. actions: <Widget>[
  269. CupertinoActionSheetAction(
  270. child: Text("remove"),
  271. isDestructiveAction: true,
  272. onPressed: () async {
  273. Navigator.of(context, rootNavigator: true).pop();
  274. final dialog = createProgressDialog(context, "removing files...");
  275. await dialog.show();
  276. try {
  277. await CollectionsService.instance.removeFromCollection(
  278. widget.collection.id, widget.selectedFiles.files.toList());
  279. await dialog.hide();
  280. widget.selectedFiles.clearAll();
  281. } catch (e, s) {
  282. _logger.severe(e, s);
  283. await dialog.hide();
  284. showGenericErrorDialog(context);
  285. }
  286. },
  287. ),
  288. ],
  289. cancelButton: CupertinoActionSheetAction(
  290. child: Text("cancel"),
  291. onPressed: () {
  292. Navigator.of(context, rootNavigator: true).pop();
  293. },
  294. ),
  295. );
  296. showCupertinoModalPopup(context: context, builder: (_) => action);
  297. }
  298. void _showDeleteSheet(BuildContext context) {
  299. final count = widget.selectedFiles.files.length;
  300. bool containsUploadedFile = false, containsLocalFile = false;
  301. for (final file in widget.selectedFiles.files) {
  302. if (file.uploadedFileID != null) {
  303. containsUploadedFile = true;
  304. }
  305. if (file.localID != null) {
  306. containsLocalFile = true;
  307. }
  308. }
  309. final actions = <Widget>[];
  310. if (containsUploadedFile && containsLocalFile) {
  311. actions.add(CupertinoActionSheetAction(
  312. child: Text("this device"),
  313. isDestructiveAction: true,
  314. onPressed: () async {
  315. Navigator.of(context, rootNavigator: true).pop();
  316. await deleteFilesOnDeviceOnly(
  317. context, widget.selectedFiles.files.toList());
  318. _clearSelectedFiles();
  319. showToast("files deleted from device");
  320. },
  321. ));
  322. actions.add(CupertinoActionSheetAction(
  323. child: Text("everywhere"),
  324. isDestructiveAction: true,
  325. onPressed: () async {
  326. Navigator.of(context, rootNavigator: true).pop();
  327. await deleteFilesFromEverywhere(
  328. context, widget.selectedFiles.files.toList());
  329. _clearSelectedFiles();
  330. },
  331. ));
  332. } else {
  333. actions.add(CupertinoActionSheetAction(
  334. child: Text("delete forever"),
  335. isDestructiveAction: true,
  336. onPressed: () async {
  337. Navigator.of(context, rootNavigator: true).pop();
  338. await deleteFilesFromEverywhere(
  339. context, widget.selectedFiles.files.toList());
  340. _clearSelectedFiles();
  341. },
  342. ));
  343. }
  344. final action = CupertinoActionSheet(
  345. title: Text("delete " +
  346. count.toString() +
  347. " file" +
  348. (count == 1 ? "" : "s") +
  349. (containsUploadedFile && containsLocalFile ? " from" : "?")),
  350. actions: actions,
  351. cancelButton: CupertinoActionSheetAction(
  352. child: Text("cancel"),
  353. onPressed: () {
  354. Navigator.of(context, rootNavigator: true).pop();
  355. },
  356. ),
  357. );
  358. showCupertinoModalPopup(context: context, builder: (_) => action);
  359. }
  360. void _clearSelectedFiles() {
  361. widget.selectedFiles.clearAll();
  362. }
  363. }