menu_item_widget.dart 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import 'package:expandable/expandable.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:photos/ente_theme_data.dart';
  4. import 'package:photos/theme/ente_theme.dart';
  5. class MenuItemWidget extends StatefulWidget {
  6. final Widget captionedTextWidget;
  7. final bool isExpandable;
  8. /// leading icon can be passed without specifing size of icon,
  9. /// this component sets size to 20x20 irrespective of passed icon's size
  10. final IconData? leadingIcon;
  11. final Color? leadingIconColor;
  12. final Widget? leadingIconWidget;
  13. // leadIconSize deafult value is 20.
  14. final double leadingIconSize;
  15. /// trailing icon can be passed without size as default size set by
  16. /// flutter is what this component expects
  17. final IconData? trailingIcon;
  18. final Color? trailingIconColor;
  19. final Widget? trailingWidget;
  20. final bool trailingIconIsMuted;
  21. final VoidCallback? onTap;
  22. final VoidCallback? onDoubleTap;
  23. final Color? menuItemColor;
  24. final bool alignCaptionedTextToLeft;
  25. final double borderRadius;
  26. final Color? pressedColor;
  27. final ExpandableController? expandableController;
  28. final bool isBottomBorderRadiusRemoved;
  29. final bool isTopBorderRadiusRemoved;
  30. /// disable gesture detector if not used
  31. final bool isGestureDetectorDisabled;
  32. const MenuItemWidget({
  33. required this.captionedTextWidget,
  34. this.isExpandable = false,
  35. this.leadingIcon,
  36. this.leadingIconColor,
  37. this.leadingIconSize = 20.0,
  38. this.leadingIconWidget,
  39. this.trailingIcon,
  40. this.trailingIconColor,
  41. this.trailingWidget,
  42. this.trailingIconIsMuted = false,
  43. this.onTap,
  44. this.onDoubleTap,
  45. this.menuItemColor,
  46. this.alignCaptionedTextToLeft = false,
  47. this.borderRadius = 4.0,
  48. this.pressedColor,
  49. this.expandableController,
  50. this.isBottomBorderRadiusRemoved = false,
  51. this.isTopBorderRadiusRemoved = false,
  52. this.isGestureDetectorDisabled = false,
  53. Key? key,
  54. }) : super(key: key);
  55. @override
  56. State<MenuItemWidget> createState() => _MenuItemWidgetState();
  57. }
  58. class _MenuItemWidgetState extends State<MenuItemWidget> {
  59. Color? menuItemColor;
  60. @override
  61. void initState() {
  62. menuItemColor = widget.menuItemColor;
  63. if (widget.expandableController != null) {
  64. widget.expandableController!.addListener(() {
  65. setState(() {});
  66. });
  67. }
  68. super.initState();
  69. }
  70. @override
  71. void didChangeDependencies() {
  72. menuItemColor = widget.menuItemColor;
  73. super.didChangeDependencies();
  74. }
  75. @override
  76. void dispose() {
  77. if (widget.expandableController != null) {
  78. widget.expandableController!.dispose();
  79. }
  80. super.dispose();
  81. }
  82. @override
  83. Widget build(BuildContext context) {
  84. return widget.isExpandable || widget.isGestureDetectorDisabled
  85. ? menuItemWidget(context)
  86. : GestureDetector(
  87. onTap: widget.onTap,
  88. onDoubleTap: widget.onDoubleTap,
  89. onTapDown: _onTapDown,
  90. onTapUp: _onTapUp,
  91. onTapCancel: _onCancel,
  92. child: menuItemWidget(context),
  93. );
  94. }
  95. Widget menuItemWidget(BuildContext context) {
  96. final enteColorScheme = Theme.of(context).colorScheme.enteTheme.colorScheme;
  97. final borderRadius = Radius.circular(widget.borderRadius);
  98. final isExpanded = widget.expandableController?.value;
  99. final bottomBorderRadius =
  100. (isExpanded != null && isExpanded) || widget.isBottomBorderRadiusRemoved
  101. ? const Radius.circular(0)
  102. : borderRadius;
  103. final topBorderRadius = widget.isTopBorderRadiusRemoved
  104. ? const Radius.circular(0)
  105. : borderRadius;
  106. return AnimatedContainer(
  107. duration: const Duration(milliseconds: 20),
  108. width: double.infinity,
  109. padding: const EdgeInsets.only(left: 16, right: 12),
  110. decoration: BoxDecoration(
  111. borderRadius: BorderRadius.only(
  112. topLeft: topBorderRadius,
  113. topRight: topBorderRadius,
  114. bottomLeft: bottomBorderRadius,
  115. bottomRight: bottomBorderRadius,
  116. ),
  117. color: menuItemColor,
  118. ),
  119. child: Row(
  120. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  121. children: [
  122. widget.alignCaptionedTextToLeft && widget.leadingIcon == null
  123. ? const SizedBox.shrink()
  124. : Padding(
  125. padding: const EdgeInsets.only(right: 10),
  126. child: SizedBox(
  127. height: widget.leadingIconSize,
  128. width: widget.leadingIconSize,
  129. child: widget.leadingIcon == null
  130. ? (widget.leadingIconWidget != null
  131. ? FittedBox(
  132. fit: BoxFit.contain,
  133. child: widget.leadingIconWidget,
  134. )
  135. : const SizedBox.shrink())
  136. : FittedBox(
  137. fit: BoxFit.contain,
  138. child: Icon(
  139. widget.leadingIcon,
  140. color: widget.leadingIconColor ??
  141. enteColorScheme.strokeBase,
  142. ),
  143. ),
  144. ),
  145. ),
  146. widget.captionedTextWidget,
  147. widget.expandableController != null
  148. ? AnimatedOpacity(
  149. duration: const Duration(milliseconds: 100),
  150. curve: Curves.easeInOut,
  151. opacity: isExpanded! ? 0 : 1,
  152. child: AnimatedSwitcher(
  153. transitionBuilder: (child, animation) {
  154. return ScaleTransition(scale: animation, child: child);
  155. },
  156. duration: const Duration(milliseconds: 200),
  157. switchInCurve: Curves.easeOut,
  158. child: isExpanded
  159. ? const SizedBox.shrink()
  160. : Icon(
  161. widget.trailingIcon,
  162. color: widget.trailingIconColor,
  163. ),
  164. ),
  165. )
  166. : widget.trailingIcon != null
  167. ? Icon(
  168. widget.trailingIcon,
  169. color: widget.trailingIconIsMuted
  170. ? enteColorScheme.strokeMuted
  171. : widget.trailingIconColor,
  172. )
  173. : widget.trailingWidget ?? const SizedBox.shrink(),
  174. ],
  175. ),
  176. );
  177. }
  178. void _onTapDown(details) {
  179. setState(() {
  180. if (widget.pressedColor == null) {
  181. hasPassedGestureCallbacks()
  182. ? menuItemColor = getEnteColorScheme(context).fillFaintPressed
  183. : menuItemColor = widget.menuItemColor;
  184. } else {
  185. menuItemColor = widget.pressedColor;
  186. }
  187. });
  188. }
  189. bool hasPassedGestureCallbacks() {
  190. return widget.onDoubleTap != null || widget.onTap != null;
  191. }
  192. void _onTapUp(details) {
  193. Future.delayed(
  194. const Duration(milliseconds: 100),
  195. () => setState(() {
  196. menuItemColor = widget.menuItemColor;
  197. }),
  198. );
  199. }
  200. void _onCancel() {
  201. setState(() {
  202. menuItemColor = widget.menuItemColor;
  203. });
  204. }
  205. }