menu_item_widget.dart 6.7 KB

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