menu_item_widget.dart 5.5 KB

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