action_sheet_widget.dart 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import 'dart:ui';
  2. import 'package:flutter/material.dart';
  3. import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
  4. import 'package:photos/core/constants.dart';
  5. import 'package:photos/theme/colors.dart';
  6. import 'package:photos/theme/effects.dart';
  7. import 'package:photos/theme/ente_theme.dart';
  8. import 'package:photos/ui/components/button_widget.dart';
  9. import 'package:photos/utils/separators_util.dart';
  10. enum ActionSheetType {
  11. defaultActionSheet,
  12. iconOnly,
  13. }
  14. ///Returns null if dismissed
  15. Future<ButtonAction?> showActionSheet({
  16. required BuildContext context,
  17. required List<ButtonWidget> buttons,
  18. ActionSheetType actionSheetType = ActionSheetType.defaultActionSheet,
  19. bool enableDrag = true,
  20. bool isDismissible = true,
  21. bool isCheckIconGreen = false,
  22. String? title,
  23. Widget? bodyWidget,
  24. String? body,
  25. String? bodyHighlight,
  26. }) {
  27. return showMaterialModalBottomSheet(
  28. backgroundColor: Colors.transparent,
  29. barrierColor: backdropFaintDark,
  30. useRootNavigator: true,
  31. context: context,
  32. isDismissible: isDismissible,
  33. enableDrag: enableDrag,
  34. builder: (_) {
  35. return ActionSheetWidget(
  36. title: title,
  37. bodyWidget: bodyWidget,
  38. body: body,
  39. bodyHighlight: bodyHighlight,
  40. actionButtons: buttons,
  41. actionSheetType: actionSheetType,
  42. isCheckIconGreen: isCheckIconGreen,
  43. );
  44. },
  45. );
  46. }
  47. class ActionSheetWidget extends StatelessWidget {
  48. final String? title;
  49. final Widget? bodyWidget;
  50. final String? body;
  51. final String? bodyHighlight;
  52. final List<ButtonWidget> actionButtons;
  53. final ActionSheetType actionSheetType;
  54. final bool isCheckIconGreen;
  55. const ActionSheetWidget({
  56. required this.actionButtons,
  57. required this.actionSheetType,
  58. required this.isCheckIconGreen,
  59. this.title,
  60. this.bodyWidget,
  61. this.body,
  62. this.bodyHighlight,
  63. super.key,
  64. });
  65. @override
  66. Widget build(BuildContext context) {
  67. final isTitleAndBodyNull =
  68. title == null && bodyWidget == null && body == null;
  69. final blur = MediaQuery.of(context).platformBrightness == Brightness.light
  70. ? blurMuted
  71. : blurBase;
  72. final extraWidth = MediaQuery.of(context).size.width - restrictedMaxWidth;
  73. final double? horizontalPadding = extraWidth > 0 ? extraWidth / 2 : null;
  74. return Padding(
  75. padding: EdgeInsets.fromLTRB(
  76. horizontalPadding ?? 12,
  77. 12,
  78. horizontalPadding ?? 12,
  79. 32,
  80. ),
  81. child: Container(
  82. decoration: BoxDecoration(boxShadow: shadowMenuLight),
  83. child: ClipRRect(
  84. borderRadius: const BorderRadius.all(Radius.circular(8)),
  85. child: BackdropFilter(
  86. filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur),
  87. child: Container(
  88. color: backdropMutedDark,
  89. child: Padding(
  90. padding: EdgeInsets.fromLTRB(
  91. 24,
  92. 24,
  93. 24,
  94. isTitleAndBodyNull ? 24 : 28,
  95. ),
  96. child: Column(
  97. mainAxisSize: MainAxisSize.min,
  98. children: [
  99. isTitleAndBodyNull
  100. ? const SizedBox.shrink()
  101. : Padding(
  102. padding: const EdgeInsets.only(bottom: 28),
  103. child: ContentContainerWidget(
  104. title: title,
  105. bodyWidget: bodyWidget,
  106. body: body,
  107. bodyHighlight: bodyHighlight,
  108. actionSheetType: actionSheetType,
  109. isCheckIconGreen: isCheckIconGreen,
  110. ),
  111. ),
  112. ActionButtons(
  113. actionButtons,
  114. ),
  115. ],
  116. ),
  117. ),
  118. ),
  119. ),
  120. ),
  121. ),
  122. );
  123. }
  124. }
  125. class ContentContainerWidget extends StatelessWidget {
  126. final String? title;
  127. final Widget? bodyWidget;
  128. final String? body;
  129. final String? bodyHighlight;
  130. final ActionSheetType actionSheetType;
  131. final bool isCheckIconGreen;
  132. const ContentContainerWidget({
  133. required this.actionSheetType,
  134. required this.isCheckIconGreen,
  135. this.title,
  136. this.bodyWidget,
  137. this.body,
  138. this.bodyHighlight,
  139. super.key,
  140. });
  141. @override
  142. Widget build(BuildContext context) {
  143. final textTheme = getEnteTextTheme(context);
  144. final bool bodyMissing = body == null && bodyWidget == null;
  145. debugPrint("body missing $bodyMissing");
  146. return Column(
  147. mainAxisSize: MainAxisSize.min,
  148. //todo: set cross axis to center when icon should be shown in place of body
  149. crossAxisAlignment: actionSheetType == ActionSheetType.defaultActionSheet
  150. ? CrossAxisAlignment.stretch
  151. : CrossAxisAlignment.center,
  152. children: [
  153. title == null
  154. ? const SizedBox.shrink()
  155. : Text(
  156. title!,
  157. style: textTheme.largeBold
  158. .copyWith(color: textBaseDark), //constant color
  159. ),
  160. title == null || bodyMissing
  161. ? const SizedBox.shrink()
  162. : const SizedBox(height: 19),
  163. actionSheetType == ActionSheetType.defaultActionSheet
  164. ? bodyMissing
  165. ? const SizedBox.shrink()
  166. : (bodyWidget != null
  167. ? bodyWidget!
  168. : Text(
  169. body!,
  170. style: textTheme.body
  171. .copyWith(color: textMutedDark), //constant color
  172. ))
  173. : Icon(
  174. Icons.check_outlined,
  175. size: 48,
  176. color: isCheckIconGreen
  177. ? getEnteColorScheme(context).primary700
  178. : strokeBaseDark,
  179. ),
  180. actionSheetType == ActionSheetType.defaultActionSheet &&
  181. bodyHighlight != null
  182. ? Padding(
  183. padding: const EdgeInsets.only(top: 19.0),
  184. child: Text(
  185. bodyHighlight!,
  186. style: textTheme.body
  187. .copyWith(color: textBaseDark), //constant color
  188. ),
  189. )
  190. : const SizedBox.shrink(),
  191. ],
  192. );
  193. }
  194. }
  195. class ActionButtons extends StatelessWidget {
  196. final List<Widget> actionButtons;
  197. const ActionButtons(this.actionButtons, {super.key});
  198. @override
  199. Widget build(BuildContext context) {
  200. final actionButtonsWithSeparators = actionButtons;
  201. return Column(
  202. children:
  203. //Separator height is 8pts in figma. -2pts here as the action
  204. //buttons are 2pts extra in height in code compared to figma because
  205. //of the border(1pt top + 1pt bottom) of action buttons.
  206. addSeparators(actionButtonsWithSeparators, const SizedBox(height: 6)),
  207. );
  208. }
  209. }