magic_util.dart 8.7 KB

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