gallery_app_bar_widget.dart 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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/billing_service.dart';
  13. import 'package:photos/services/collections_service.dart';
  14. import 'package:photos/ui/create_collection_page.dart';
  15. import 'package:photos/ui/share_collection_widget.dart';
  16. import 'package:photos/utils/delete_file_util.dart';
  17. import 'package:photos/utils/dialog_util.dart';
  18. import 'package:photos/utils/share_util.dart';
  19. import 'package:photos/utils/toast_util.dart';
  20. enum GalleryAppBarType {
  21. homepage,
  22. local_folder,
  23. shared_collection,
  24. 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 = List<Widget>();
  97. if (Configuration.instance.hasConfiguredAccount() &&
  98. (widget.type == GalleryAppBarType.local_folder ||
  99. widget.type == GalleryAppBarType.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. if (collection == null) {
  112. if (widget.type == GalleryAppBarType.local_folder) {
  113. collection =
  114. CollectionsService.instance.getCollectionForPath(widget.path);
  115. if (collection == null) {
  116. final dialog = createProgressDialog(context, "please wait...");
  117. await dialog.show();
  118. try {
  119. collection = await CollectionsService.instance
  120. .getOrCreateForPath(widget.path);
  121. await dialog.hide();
  122. } catch (e, s) {
  123. _logger.severe(e, s);
  124. await dialog.hide();
  125. showGenericErrorDialog(context);
  126. }
  127. }
  128. } else {
  129. throw Exception(
  130. "Cannot create a collection of type" + widget.type.toString());
  131. }
  132. }
  133. return showDialog<void>(
  134. context: context,
  135. builder: (BuildContext context) {
  136. return SharingDialog(collection);
  137. },
  138. );
  139. }
  140. Future<void> _createAlbum() async {
  141. Navigator.push(
  142. context,
  143. PageTransition(
  144. type: PageTransitionType.bottomToTop,
  145. child: CreateCollectionPage(
  146. widget.selectedFiles,
  147. )));
  148. }
  149. List<Widget> _getActions(BuildContext context) {
  150. List<Widget> actions = List<Widget>();
  151. if (BillingService.instance.hasActiveSubscription()) {
  152. actions.add(IconButton(
  153. icon:
  154. Icon(Platform.isAndroid ? Icons.add_outlined : CupertinoIcons.add),
  155. onPressed: () {
  156. _createAlbum();
  157. },
  158. ));
  159. }
  160. actions.add(IconButton(
  161. icon: Icon(
  162. Platform.isAndroid ? Icons.share_outlined : CupertinoIcons.share),
  163. onPressed: () {
  164. _shareSelected(context);
  165. },
  166. ));
  167. if (widget.type == GalleryAppBarType.homepage ||
  168. widget.type == GalleryAppBarType.local_folder) {
  169. actions.add(IconButton(
  170. icon: Icon(
  171. Platform.isAndroid ? Icons.delete_outline : CupertinoIcons.delete),
  172. onPressed: () {
  173. _showDeleteSheet(context);
  174. },
  175. ));
  176. } else if (widget.type == GalleryAppBarType.collection ||
  177. (widget.type == GalleryAppBarType.shared_collection &&
  178. widget.collection.owner.id == Configuration.instance.getUserID())) {
  179. if (widget.collection.type == CollectionType.folder) {
  180. actions.add(IconButton(
  181. icon: Icon(Platform.isAndroid
  182. ? Icons.delete_outline
  183. : CupertinoIcons.delete),
  184. onPressed: () {
  185. _showDeleteSheet(context);
  186. },
  187. ));
  188. } else {
  189. actions.add(IconButton(
  190. icon: Icon(Icons.remove_circle_outline_rounded),
  191. onPressed: () {
  192. _showRemoveFromCollectionSheet(context);
  193. },
  194. ));
  195. }
  196. }
  197. return actions;
  198. }
  199. void _shareSelected(BuildContext context) {
  200. share(context, widget.selectedFiles.files.toList());
  201. }
  202. void _showRemoveFromCollectionSheet(BuildContext context) {
  203. final count = widget.selectedFiles.files.length;
  204. final action = CupertinoActionSheet(
  205. title: Text("remove " +
  206. count.toString() +
  207. " file" +
  208. (count == 1 ? "" : "s") +
  209. " from " +
  210. widget.collection.name +
  211. "?"),
  212. actions: <Widget>[
  213. CupertinoActionSheetAction(
  214. child: Text("remove"),
  215. isDestructiveAction: true,
  216. onPressed: () async {
  217. Navigator.of(context, rootNavigator: true).pop();
  218. final dialog = createProgressDialog(context, "removing files...");
  219. await dialog.show();
  220. try {
  221. CollectionsService.instance.removeFromCollection(
  222. widget.collection.id, widget.selectedFiles.files.toList());
  223. await dialog.hide();
  224. widget.selectedFiles.clearAll();
  225. } catch (e, s) {
  226. _logger.severe(e, s);
  227. await dialog.hide();
  228. showGenericErrorDialog(context);
  229. }
  230. },
  231. ),
  232. ],
  233. cancelButton: CupertinoActionSheetAction(
  234. child: Text("cancel"),
  235. onPressed: () {
  236. Navigator.of(context, rootNavigator: true).pop();
  237. },
  238. ),
  239. );
  240. showCupertinoModalPopup(context: context, builder: (_) => action);
  241. }
  242. void _showDeleteSheet(BuildContext context) {
  243. final count = widget.selectedFiles.files.length;
  244. bool containsUploadedFile = false, containsLocalFile = false;
  245. for (final file in widget.selectedFiles.files) {
  246. if (file.uploadedFileID != null) {
  247. containsUploadedFile = true;
  248. }
  249. if (file.localID != null) {
  250. containsLocalFile = true;
  251. }
  252. }
  253. final actions = List<Widget>();
  254. if (containsUploadedFile && containsLocalFile) {
  255. actions.add(CupertinoActionSheetAction(
  256. child: Text("this device"),
  257. isDestructiveAction: true,
  258. onPressed: () async {
  259. Navigator.of(context, rootNavigator: true).pop();
  260. await deleteFilesOnDeviceOnly(
  261. context, widget.selectedFiles.files.toList());
  262. _clearSelectedFiles();
  263. showToast("files deleted from device");
  264. },
  265. ));
  266. actions.add(CupertinoActionSheetAction(
  267. child: Text("everywhere"),
  268. isDestructiveAction: true,
  269. onPressed: () async {
  270. Navigator.of(context, rootNavigator: true).pop();
  271. await deleteFilesFromEverywhere(
  272. context, widget.selectedFiles.files.toList());
  273. _clearSelectedFiles();
  274. },
  275. ));
  276. } else {
  277. actions.add(CupertinoActionSheetAction(
  278. child: Text("delete forever"),
  279. isDestructiveAction: true,
  280. onPressed: () async {
  281. Navigator.of(context, rootNavigator: true).pop();
  282. await deleteFilesFromEverywhere(
  283. context, widget.selectedFiles.files.toList());
  284. _clearSelectedFiles();
  285. },
  286. ));
  287. }
  288. final action = CupertinoActionSheet(
  289. title: Text("delete " +
  290. count.toString() +
  291. " file" +
  292. (count == 1 ? "" : "s") +
  293. (containsUploadedFile && containsLocalFile ? " from" : "?")),
  294. actions: actions,
  295. cancelButton: CupertinoActionSheetAction(
  296. child: Text("cancel"),
  297. onPressed: () {
  298. Navigator.of(context, rootNavigator: true).pop();
  299. },
  300. ),
  301. );
  302. showCupertinoModalPopup(context: context, builder: (_) => action);
  303. }
  304. void _clearSelectedFiles() {
  305. widget.selectedFiles.clearAll();
  306. }
  307. }