bottom_action_bar_widget.dart 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. import 'dart:ui';
  2. import 'package:expandable/expandable.dart';
  3. import 'package:flutter/cupertino.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:photos/models/selected_files.dart';
  6. import 'package:photos/theme/effects.dart';
  7. import 'package:photos/theme/ente_theme.dart';
  8. import 'package:photos/ui/components/bottom_action_bar/action_bar_widget.dart';
  9. import 'package:photos/ui/components/icon_button_widget.dart';
  10. import 'package:photos/utils/delete_file_util.dart';
  11. import 'package:photos/utils/share_util.dart';
  12. import 'package:photos/utils/toast_util.dart';
  13. class BottomActionBarWidget extends StatelessWidget {
  14. final String? text;
  15. final List<Widget>? iconButtons;
  16. final Widget expandedMenu;
  17. final SelectedFiles? selectedFiles;
  18. final VoidCallback? onCancel;
  19. final GlobalKey shareButtonKey = GlobalKey();
  20. BottomActionBarWidget({
  21. required this.expandedMenu,
  22. this.selectedFiles,
  23. this.text,
  24. this.iconButtons,
  25. this.onCancel,
  26. super.key,
  27. });
  28. final ExpandableController _expandableController =
  29. ExpandableController(initialExpanded: false);
  30. @override
  31. @override
  32. Widget build(BuildContext context) {
  33. final colorScheme = getEnteColorScheme(context);
  34. final textTheme = getEnteTextTheme(context);
  35. //todo : restrict width of column
  36. return ClipRRect(
  37. child: BackdropFilter(
  38. filter: ImageFilter.blur(sigmaX: blurBase, sigmaY: blurBase),
  39. child: Container(
  40. color: colorScheme.backdropBase,
  41. padding: const EdgeInsets.only(
  42. top: 4,
  43. // bottom: !_expandableController.expanded &&
  44. // ((widget.selectedFiles?.files.isNotEmpty) ?? false)
  45. // ? 24
  46. // : 36,
  47. bottom: 24,
  48. ),
  49. child: Column(
  50. mainAxisSize: MainAxisSize.min,
  51. children: [
  52. ExpandableNotifier(
  53. controller: _expandableController,
  54. child: ExpandablePanel(
  55. theme: _getExpandableTheme(),
  56. header: Padding(
  57. padding: EdgeInsets.symmetric(
  58. horizontal: text == null ? 12 : 0,
  59. ),
  60. child: ActionBarWidget(
  61. selectedFiles: selectedFiles,
  62. text: text,
  63. iconButtons: _iconButtons(context),
  64. ),
  65. ),
  66. expanded: expandedMenu,
  67. collapsed: const SizedBox.shrink(),
  68. controller: _expandableController,
  69. ),
  70. ),
  71. GestureDetector(
  72. onTap: () {
  73. //unselect all files here
  74. //or collapse the expansion panel
  75. onCancel?.call();
  76. _expandableController.value = false;
  77. },
  78. child: Container(
  79. width: double.infinity,
  80. padding: const EdgeInsets.symmetric(
  81. horizontal: 16,
  82. vertical: 14,
  83. ),
  84. child: Center(
  85. child: Text(
  86. "Cancel",
  87. style: textTheme.bodyBold
  88. .copyWith(color: colorScheme.blurTextBase),
  89. ),
  90. ),
  91. ),
  92. )
  93. ],
  94. ),
  95. ),
  96. ),
  97. );
  98. }
  99. List<Widget> _iconButtons(BuildContext context) {
  100. final iconButtonsWithExpansionIcon = <Widget?>[
  101. IconButtonWidget(
  102. icon: Icons.delete_outlined,
  103. iconButtonType: IconButtonType.primary,
  104. onTap: () => _showDeleteSheet(context),
  105. ),
  106. IconButtonWidget(
  107. icon: Icons.ios_share_outlined,
  108. iconButtonType: IconButtonType.primary,
  109. onTap: () => _shareSelected(context),
  110. ),
  111. ExpansionIconWidget(expandableController: _expandableController)
  112. ];
  113. iconButtonsWithExpansionIcon.removeWhere((element) => element == null);
  114. return iconButtonsWithExpansionIcon as List<Widget>;
  115. }
  116. void _showDeleteSheet(BuildContext context) {
  117. final count = selectedFiles!.files.length;
  118. bool containsUploadedFile = false, containsLocalFile = false;
  119. for (final file in selectedFiles!.files) {
  120. if (file.uploadedFileID != null) {
  121. containsUploadedFile = true;
  122. }
  123. if (file.localID != null) {
  124. containsLocalFile = true;
  125. }
  126. }
  127. final actions = <Widget>[];
  128. if (containsUploadedFile && containsLocalFile) {
  129. actions.add(
  130. CupertinoActionSheetAction(
  131. isDestructiveAction: true,
  132. onPressed: () async {
  133. Navigator.of(context, rootNavigator: true).pop();
  134. await deleteFilesOnDeviceOnly(
  135. context,
  136. selectedFiles!.files.toList(),
  137. );
  138. _clearSelectedFiles();
  139. showToast(context, "Files deleted from device");
  140. },
  141. child: const Text("Device"),
  142. ),
  143. );
  144. actions.add(
  145. CupertinoActionSheetAction(
  146. isDestructiveAction: true,
  147. onPressed: () async {
  148. Navigator.of(context, rootNavigator: true).pop();
  149. await deleteFilesFromRemoteOnly(
  150. context,
  151. selectedFiles!.files.toList(),
  152. );
  153. _clearSelectedFiles();
  154. showShortToast(context, "Moved to trash");
  155. },
  156. child: const Text("ente"),
  157. ),
  158. );
  159. actions.add(
  160. CupertinoActionSheetAction(
  161. isDestructiveAction: true,
  162. onPressed: () async {
  163. Navigator.of(context, rootNavigator: true).pop();
  164. await deleteFilesFromEverywhere(
  165. context,
  166. selectedFiles!.files.toList(),
  167. );
  168. _clearSelectedFiles();
  169. },
  170. child: const Text("Everywhere"),
  171. ),
  172. );
  173. } else {
  174. actions.add(
  175. CupertinoActionSheetAction(
  176. isDestructiveAction: true,
  177. onPressed: () async {
  178. Navigator.of(context, rootNavigator: true).pop();
  179. await deleteFilesFromEverywhere(
  180. context,
  181. selectedFiles!.files.toList(),
  182. );
  183. _clearSelectedFiles();
  184. },
  185. child: const Text("Delete"),
  186. ),
  187. );
  188. }
  189. final action = CupertinoActionSheet(
  190. title: Text(
  191. "Delete " +
  192. count.toString() +
  193. " file" +
  194. (count == 1 ? "" : "s") +
  195. (containsUploadedFile && containsLocalFile ? " from" : "?"),
  196. ),
  197. actions: actions,
  198. cancelButton: CupertinoActionSheetAction(
  199. child: const Text("Cancel"),
  200. onPressed: () {
  201. Navigator.of(context, rootNavigator: true).pop();
  202. },
  203. ),
  204. );
  205. showCupertinoModalPopup(
  206. context: context,
  207. builder: (_) => action,
  208. barrierColor: Colors.black.withOpacity(0.75),
  209. );
  210. }
  211. void _shareSelected(BuildContext context) {
  212. share(
  213. context,
  214. selectedFiles!.files.toList(),
  215. shareButtonKey: shareButtonKey,
  216. );
  217. }
  218. void _clearSelectedFiles() {
  219. selectedFiles!.clearAll();
  220. }
  221. ExpandableThemeData _getExpandableTheme() {
  222. return const ExpandableThemeData(
  223. hasIcon: false,
  224. useInkWell: false,
  225. tapBodyToCollapse: false,
  226. tapBodyToExpand: false,
  227. tapHeaderToExpand: false,
  228. animationDuration: Duration(milliseconds: 400),
  229. );
  230. }
  231. }
  232. class ExpansionIconWidget extends StatefulWidget {
  233. final ExpandableController expandableController;
  234. const ExpansionIconWidget({required this.expandableController, super.key});
  235. @override
  236. State<ExpansionIconWidget> createState() => _ExpansionIconWidgetState();
  237. }
  238. class _ExpansionIconWidgetState extends State<ExpansionIconWidget> {
  239. @override
  240. void initState() {
  241. widget.expandableController.addListener(_expandableListener);
  242. super.initState();
  243. }
  244. @override
  245. void dispose() {
  246. widget.expandableController.removeListener(_expandableListener);
  247. super.dispose();
  248. }
  249. @override
  250. Widget build(BuildContext context) {
  251. return AnimatedSwitcher(
  252. duration: const Duration(milliseconds: 200),
  253. switchInCurve: Curves.easeInOutExpo,
  254. child: widget.expandableController.value
  255. ? IconButtonWidget(
  256. key: const ValueKey<bool>(false),
  257. onTap: () {
  258. widget.expandableController.value = false;
  259. setState(() {});
  260. },
  261. icon: Icons.expand_more_outlined,
  262. iconButtonType: IconButtonType.primary,
  263. )
  264. : IconButtonWidget(
  265. key: const ValueKey<bool>(true),
  266. onTap: () {
  267. widget.expandableController.value = true;
  268. setState(() {});
  269. },
  270. icon: Icons.more_horiz_outlined,
  271. iconButtonType: IconButtonType.primary,
  272. ),
  273. );
  274. }
  275. _expandableListener() {
  276. if (mounted) {
  277. setState(() {});
  278. }
  279. }
  280. }