gallery_app_bar_widget.dart 10.0 KB

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