magic_util.dart 8.9 KB

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