magic_util.dart 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  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. enum _VisibilityAction { hide, unHide, archive, unarchive }
  22. Future<void> changeVisibility(
  23. BuildContext context,
  24. List<File> files,
  25. int newVisibility,
  26. ) async {
  27. final dialog = createProgressDialog(
  28. context,
  29. newVisibility == archiveVisibility
  30. ? S.of(context).archiving
  31. : S.of(context).unarchiving,
  32. );
  33. await dialog.show();
  34. try {
  35. await FileMagicService.instance.changeVisibility(files, newVisibility);
  36. showShortToast(
  37. context,
  38. newVisibility == archiveVisibility
  39. ? S.of(context).successfullyArchived
  40. : S.of(context).successfullyUnarchived,
  41. );
  42. await dialog.hide();
  43. } catch (e, s) {
  44. _logger.severe("failed to update file visibility", e, s);
  45. await dialog.hide();
  46. rethrow;
  47. }
  48. }
  49. Future<void> changeCollectionVisibility(
  50. BuildContext context, {
  51. required Collection collection,
  52. required int newVisibility,
  53. required int prevVisibility,
  54. bool isOwner = true,
  55. }) async {
  56. final visibilityAction =
  57. _getVisibilityAction(context, newVisibility, prevVisibility);
  58. final dialog = createProgressDialog(
  59. context,
  60. _visActionProgressDialogText(
  61. context,
  62. visibilityAction,
  63. ),
  64. );
  65. await dialog.show();
  66. try {
  67. final Map<String, dynamic> update = {magicKeyVisibility: newVisibility};
  68. if (isOwner) {
  69. await CollectionsService.instance.updateMagicMetadata(collection, update);
  70. } else {
  71. await CollectionsService.instance
  72. .updateShareeMagicMetadata(collection, update);
  73. }
  74. // Force reload home gallery to pull in/remove the now visibility changed
  75. // files
  76. Bus.instance.fire(
  77. ForceReloadHomeGalleryEvent(
  78. "CollectionVisibilityChange: $visibilityAction",
  79. ),
  80. );
  81. showShortToast(
  82. context,
  83. _visActionSuccessfulText(
  84. context,
  85. visibilityAction,
  86. ),
  87. );
  88. await dialog.hide();
  89. } catch (e, s) {
  90. _logger.severe("failed to update collection visibility", e, s);
  91. await dialog.hide();
  92. rethrow;
  93. }
  94. }
  95. Future<void> changeSortOrder(
  96. BuildContext context,
  97. Collection collection,
  98. bool sortedInAscOrder,
  99. ) async {
  100. try {
  101. final Map<String, dynamic> update = {"asc": sortedInAscOrder};
  102. await CollectionsService.instance
  103. .updatePublicMagicMetadata(collection, update);
  104. Bus.instance.fire(
  105. CollectionMetaEvent(collection.id, CollectionMetaEventType.sortChanged),
  106. );
  107. } catch (e, s) {
  108. _logger.severe("failed to update collection visibility", e, s);
  109. showShortToast(context, S.of(context).somethingWentWrong);
  110. rethrow;
  111. }
  112. }
  113. Future<void> updateOrder(
  114. BuildContext context,
  115. Collection collection,
  116. int order,
  117. ) async {
  118. try {
  119. final Map<String, dynamic> update = {
  120. orderKey: order,
  121. };
  122. await CollectionsService.instance.updateMagicMetadata(collection, update);
  123. Bus.instance.fire(
  124. CollectionMetaEvent(collection.id, CollectionMetaEventType.orderChanged),
  125. );
  126. } catch (e, s) {
  127. _logger.severe("failed to update order", e, s);
  128. showShortToast(context, S.of(context).somethingWentWrong);
  129. rethrow;
  130. }
  131. }
  132. // changeCoverPhoto is used to change cover photo for a collection. To reset to
  133. // default cover photo, pass uploadedFileID as 0
  134. Future<void> changeCoverPhoto(
  135. BuildContext context,
  136. Collection collection,
  137. int uploadedFileID,
  138. ) async {
  139. try {
  140. final Map<String, dynamic> update = {"coverID": uploadedFileID};
  141. await CollectionsService.instance
  142. .updatePublicMagicMetadata(collection, update);
  143. Bus.instance.fire(
  144. CollectionUpdatedEvent(
  145. collection.id,
  146. <File>[],
  147. "cover_change",
  148. type: EventType.coverChanged,
  149. ),
  150. );
  151. } catch (e, s) {
  152. _logger.severe("failed to update cover", e, s);
  153. showShortToast(context, S.of(context).somethingWentWrong);
  154. rethrow;
  155. }
  156. }
  157. Future<bool> editTime(
  158. BuildContext context,
  159. List<File> files,
  160. int editedTime,
  161. ) async {
  162. try {
  163. await _updatePublicMetadata(
  164. context,
  165. files,
  166. editTimeKey,
  167. editedTime,
  168. );
  169. return true;
  170. } catch (e) {
  171. showShortToast(context, S.of(context).somethingWentWrong);
  172. return false;
  173. }
  174. }
  175. Future<void> editFilename(
  176. BuildContext context,
  177. File file,
  178. ) async {
  179. final fileName = file.displayName;
  180. final nameWithoutExt = basenameWithoutExtension(fileName);
  181. final extName = extension(fileName);
  182. final result = await showTextInputDialog(
  183. context,
  184. title: S.of(context).renameFile,
  185. submitButtonLabel: S.of(context).rename,
  186. initialValue: nameWithoutExt,
  187. message: extName.toUpperCase(),
  188. alignMessage: Alignment.centerRight,
  189. hintText: S.of(context).enterFileName,
  190. maxLength: 50,
  191. alwaysShowSuccessState: true,
  192. onSubmit: (String text) async {
  193. if (text.isEmpty || text.trim() == nameWithoutExt.trim()) {
  194. return;
  195. }
  196. final newName = text + extName;
  197. await _updatePublicMetadata(
  198. context,
  199. List.of([file]),
  200. editNameKey,
  201. newName,
  202. showProgressDialogs: false,
  203. showDoneToast: false,
  204. );
  205. },
  206. );
  207. if (result is Exception) {
  208. _logger.severe("Failed to rename file");
  209. showGenericErrorDialog(context: context);
  210. }
  211. }
  212. Future<bool> editFileCaption(
  213. BuildContext? context,
  214. File file,
  215. String caption,
  216. ) async {
  217. try {
  218. await _updatePublicMetadata(
  219. context,
  220. [file],
  221. captionKey,
  222. caption,
  223. showDoneToast: false,
  224. );
  225. return true;
  226. } catch (e) {
  227. if (context != null) {
  228. showShortToast(context, S.of(context).somethingWentWrong);
  229. }
  230. return false;
  231. }
  232. }
  233. Future<void> _updatePublicMetadata(
  234. BuildContext? context,
  235. List<File> files,
  236. String key,
  237. dynamic value, {
  238. bool showDoneToast = true,
  239. bool showProgressDialogs = true,
  240. }) async {
  241. if (files.isEmpty) {
  242. return;
  243. }
  244. ProgressDialog? dialog;
  245. if (context != null && showProgressDialogs) {
  246. dialog = createProgressDialog(context, S.of(context).pleaseWait);
  247. await dialog.show();
  248. }
  249. try {
  250. final Map<String, dynamic> update = {key: value};
  251. await FileMagicService.instance.updatePublicMagicMetadata(files, update);
  252. if (context != null) {
  253. if (showDoneToast) {
  254. showShortToast(context, S.of(context).done);
  255. }
  256. await dialog?.hide();
  257. }
  258. if (_shouldReloadGallery(key)) {
  259. Bus.instance.fire(ForceReloadHomeGalleryEvent("FileMetadataChange-$key"));
  260. }
  261. } catch (e, s) {
  262. _logger.severe("failed to update $key = $value", e, s);
  263. if (context != null) {
  264. await dialog?.hide();
  265. }
  266. rethrow;
  267. }
  268. }
  269. bool _shouldReloadGallery(String key) {
  270. return key == editTimeKey;
  271. }
  272. _visActionProgressDialogText(BuildContext context, _VisibilityAction action) {
  273. switch (action) {
  274. case _VisibilityAction.archive:
  275. return S.of(context).archiving;
  276. case _VisibilityAction.hide:
  277. return S.of(context).hiding;
  278. case _VisibilityAction.unarchive:
  279. return S.of(context).unarchiving;
  280. case _VisibilityAction.unHide:
  281. return S.of(context).unhiding;
  282. }
  283. }
  284. _visActionSuccessfulText(BuildContext context, _VisibilityAction action) {
  285. switch (action) {
  286. case _VisibilityAction.archive:
  287. return S.of(context).successfullyArchived;
  288. case _VisibilityAction.hide:
  289. return S.of(context).successfullyHid;
  290. case _VisibilityAction.unarchive:
  291. return S.of(context).successfullyUnarchived;
  292. case _VisibilityAction.unHide:
  293. return S.of(context).successfullyUnhid;
  294. }
  295. }
  296. _VisibilityAction _getVisibilityAction(
  297. context,
  298. int newVisibility,
  299. int prevVisibility,
  300. ) {
  301. if (newVisibility == archiveVisibility) {
  302. return _VisibilityAction.archive;
  303. } else if (newVisibility == hiddenVisibility) {
  304. return _VisibilityAction.hide;
  305. } else if (newVisibility == visibleVisibility &&
  306. prevVisibility == archiveVisibility) {
  307. return _VisibilityAction.unarchive;
  308. } else {
  309. return _VisibilityAction.unHide;
  310. }
  311. }