magic_util.dart 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import 'package:flutter/material.dart';
  2. import 'package:logging/logging.dart';
  3. import 'package:path/path.dart';
  4. import 'package:photos/core/event_bus.dart';
  5. import "package:photos/events/collection_meta_event.dart";
  6. import "package:photos/events/collection_updated_event.dart";
  7. import "package:photos/events/files_updated_event.dart";
  8. import 'package:photos/events/force_reload_home_gallery_event.dart';
  9. import "package:photos/generated/l10n.dart";
  10. import 'package:photos/models/collection.dart';
  11. import 'package:photos/models/file.dart';
  12. import "package:photos/models/metadata/collection_magic.dart";
  13. import "package:photos/models/metadata/common_keys.dart";
  14. import "package:photos/models/metadata/file_magic.dart";
  15. import 'package:photos/services/collections_service.dart';
  16. import 'package:photos/services/file_magic_service.dart';
  17. import 'package:photos/ui/common/progress_dialog.dart';
  18. import 'package:photos/utils/dialog_util.dart';
  19. import 'package:photos/utils/toast_util.dart';
  20. final _logger = Logger('MagicUtil');
  21. Future<void> changeVisibility(
  22. BuildContext context,
  23. List<File> files,
  24. int newVisibility,
  25. ) async {
  26. final dialog = createProgressDialog(
  27. context,
  28. newVisibility == archiveVisibility
  29. ? S.of(context).archiving
  30. : S.of(context).unarchiving,
  31. );
  32. await dialog.show();
  33. try {
  34. await FileMagicService.instance.changeVisibility(files, newVisibility);
  35. showShortToast(
  36. context,
  37. newVisibility == archiveVisibility
  38. ? S.of(context).successfullyArchived
  39. : S.of(context).successfullyUnarchived,
  40. );
  41. await dialog.hide();
  42. } catch (e, s) {
  43. _logger.severe("failed to update file visibility", e, s);
  44. await dialog.hide();
  45. rethrow;
  46. }
  47. }
  48. Future<void> changeCollectionVisibility(
  49. BuildContext context,
  50. Collection collection,
  51. int newVisibility, {
  52. bool isOwner = true,
  53. }) async {
  54. final dialog = createProgressDialog(
  55. context,
  56. newVisibility == archiveVisibility
  57. ? S.of(context).archiving
  58. : S.of(context).unarchiving,
  59. );
  60. await dialog.show();
  61. try {
  62. final Map<String, dynamic> update = {magicKeyVisibility: newVisibility};
  63. if (isOwner) {
  64. await CollectionsService.instance.updateMagicMetadata(collection, update);
  65. } else {
  66. await CollectionsService.instance
  67. .updateShareeMagicMetadata(collection, update);
  68. }
  69. // Force reload home gallery to pull in the now unarchived files
  70. Bus.instance.fire(ForceReloadHomeGalleryEvent("CollectionArchiveChange"));
  71. showShortToast(
  72. context,
  73. newVisibility == archiveVisibility
  74. ? S.of(context).successfullyArchived
  75. : S.of(context).successfullyUnarchived,
  76. );
  77. await dialog.hide();
  78. } catch (e, s) {
  79. _logger.severe("failed to update collection visibility", e, s);
  80. await dialog.hide();
  81. rethrow;
  82. }
  83. }
  84. Future<void> changeSortOrder(
  85. BuildContext context,
  86. Collection collection,
  87. bool sortedInAscOrder,
  88. ) async {
  89. try {
  90. final Map<String, dynamic> update = {"asc": sortedInAscOrder};
  91. await CollectionsService.instance
  92. .updatePublicMagicMetadata(collection, update);
  93. Bus.instance.fire(
  94. CollectionMetaEvent(collection.id, CollectionMetaEventType.sortChanged),
  95. );
  96. } catch (e, s) {
  97. _logger.severe("failed to update collection visibility", e, s);
  98. showShortToast(context, S.of(context).somethingWentWrong);
  99. rethrow;
  100. }
  101. }
  102. Future<void> updateOrder(
  103. BuildContext context,
  104. Collection collection,
  105. int order,
  106. ) async {
  107. try {
  108. final Map<String, dynamic> update = {
  109. orderKey: order,
  110. };
  111. await CollectionsService.instance.updateMagicMetadata(collection, update);
  112. Bus.instance.fire(
  113. CollectionMetaEvent(collection.id, CollectionMetaEventType.orderChanged),
  114. );
  115. } catch (e, s) {
  116. _logger.severe("failed to update order", e, s);
  117. showShortToast(context, S.of(context).somethingWentWrong);
  118. rethrow;
  119. }
  120. }
  121. // changeCoverPhoto is used to change cover photo for a collection. To reset to
  122. // default cover photo, pass uploadedFileID as 0
  123. Future<void> changeCoverPhoto(
  124. BuildContext context,
  125. Collection collection,
  126. int uploadedFileID,
  127. ) async {
  128. try {
  129. final Map<String, dynamic> update = {"coverID": uploadedFileID};
  130. await CollectionsService.instance
  131. .updatePublicMagicMetadata(collection, update);
  132. Bus.instance.fire(
  133. CollectionUpdatedEvent(
  134. collection.id,
  135. <File>[],
  136. "cover_change",
  137. type: EventType.coverChanged,
  138. ),
  139. );
  140. } catch (e, s) {
  141. _logger.severe("failed to update cover", e, s);
  142. showShortToast(context, S.of(context).somethingWentWrong);
  143. rethrow;
  144. }
  145. }
  146. Future<bool> editTime(
  147. BuildContext context,
  148. List<File> files,
  149. int editedTime,
  150. ) async {
  151. try {
  152. await _updatePublicMetadata(
  153. context,
  154. files,
  155. editTimeKey,
  156. editedTime,
  157. );
  158. return true;
  159. } catch (e) {
  160. showShortToast(context, S.of(context).somethingWentWrong);
  161. return false;
  162. }
  163. }
  164. Future<void> editFilename(
  165. BuildContext context,
  166. File file,
  167. ) async {
  168. final fileName = file.displayName;
  169. final nameWithoutExt = basenameWithoutExtension(fileName);
  170. final extName = extension(fileName);
  171. final result = await showTextInputDialog(
  172. context,
  173. title: S.of(context).renameFile,
  174. submitButtonLabel: S.of(context).rename,
  175. initialValue: nameWithoutExt,
  176. message: extName.toUpperCase(),
  177. alignMessage: Alignment.centerRight,
  178. hintText: S.of(context).enterFileName,
  179. maxLength: 50,
  180. alwaysShowSuccessState: true,
  181. onSubmit: (String text) async {
  182. if (text.isEmpty || text.trim() == nameWithoutExt.trim()) {
  183. return;
  184. }
  185. final newName = text + extName;
  186. await _updatePublicMetadata(
  187. context,
  188. List.of([file]),
  189. editNameKey,
  190. newName,
  191. showProgressDialogs: false,
  192. showDoneToast: false,
  193. );
  194. },
  195. );
  196. if (result is Exception) {
  197. _logger.severe("Failed to rename file");
  198. showGenericErrorDialog(context: context);
  199. }
  200. }
  201. Future<bool> editFileCaption(
  202. BuildContext? context,
  203. File file,
  204. String caption,
  205. ) async {
  206. try {
  207. await _updatePublicMetadata(
  208. context,
  209. [file],
  210. captionKey,
  211. caption,
  212. showDoneToast: false,
  213. );
  214. return true;
  215. } catch (e) {
  216. if (context != null) {
  217. showShortToast(context, S.of(context).somethingWentWrong);
  218. }
  219. return false;
  220. }
  221. }
  222. Future<void> _updatePublicMetadata(
  223. BuildContext? context,
  224. List<File> files,
  225. String key,
  226. dynamic value, {
  227. bool showDoneToast = true,
  228. bool showProgressDialogs = true,
  229. }) async {
  230. if (files.isEmpty) {
  231. return;
  232. }
  233. ProgressDialog? dialog;
  234. if (context != null && showProgressDialogs) {
  235. dialog = createProgressDialog(context, S.of(context).pleaseWait);
  236. await dialog.show();
  237. }
  238. try {
  239. final Map<String, dynamic> update = {key: value};
  240. await FileMagicService.instance.updatePublicMagicMetadata(files, update);
  241. if (context != null) {
  242. if (showDoneToast) {
  243. showShortToast(context, S.of(context).done);
  244. }
  245. await dialog?.hide();
  246. }
  247. if (_shouldReloadGallery(key)) {
  248. Bus.instance.fire(ForceReloadHomeGalleryEvent("FileMetadataChange-$key"));
  249. }
  250. } catch (e, s) {
  251. _logger.severe("failed to update $key = $value", e, s);
  252. if (context != null) {
  253. await dialog?.hide();
  254. }
  255. rethrow;
  256. }
  257. }
  258. bool _shouldReloadGallery(String key) {
  259. return key == editTimeKey;
  260. }