ente/lib/ui/components/menu_item_widget/menu_item_widget.dart

290 lines
9.4 KiB
Dart
Raw Normal View History

import 'package:expandable/expandable.dart';
2022-09-28 05:00:43 +00:00
import 'package:flutter/material.dart';
import 'package:photos/models/execution_states.dart';
import 'package:photos/models/typedefs.dart';
import 'package:photos/theme/ente_theme.dart';
2023-01-31 12:21:57 +00:00
import 'package:photos/ui/components/menu_item_widget/menu_item_child_widgets.dart';
import 'package:photos/utils/debouncer.dart';
class MenuItemWidget extends StatefulWidget {
2022-09-28 12:58:07 +00:00
final Widget captionedTextWidget;
2022-10-22 06:26:47 +00:00
final bool isExpandable;
/// leading icon can be passed without specifing size of icon,
/// this component sets size to 20x20 irrespective of passed icon's size
final IconData? leadingIcon;
final Color? leadingIconColor;
final Widget? leadingIconWidget;
// leadIconSize deafult value is 20.
final double leadingIconSize;
/// trailing icon can be passed without size as default size set by
/// flutter is what this component expects
2022-09-29 12:21:31 +00:00
final IconData? trailingIcon;
2022-12-31 18:33:12 +00:00
final Color? trailingIconColor;
2022-12-06 18:03:39 +00:00
final Widget? trailingWidget;
final bool trailingIconIsMuted;
/// If provided, add this much extra spacing to the right of the trailing icon.
final double trailingExtraMargin;
final FutureVoidCallback? onTap;
final VoidCallback? onDoubleTap;
2023-04-17 07:13:55 +00:00
final VoidCallback? onLongPress;
2022-09-29 12:21:31 +00:00
final Color? menuItemColor;
final bool alignCaptionedTextToLeft;
// singleBorderRadius is applied to the border when it's a standalone menu item.
// Widget will apply singleBorderRadius if value of both isTopBorderRadiusRemoved
// and isBottomBorderRadiusRemoved is false. Otherwise, multipleBorderRadius will
// be applied
final double singleBorderRadius;
final double multipleBorderRadius;
2022-10-21 13:37:57 +00:00
final Color? pressedColor;
final ExpandableController? expandableController;
final bool isBottomBorderRadiusRemoved;
final bool isTopBorderRadiusRemoved;
/// disable gesture detector if not used
final bool isGestureDetectorDisabled;
///Success state will not be shown if this flag is set to true, only idle and
///loading state
final bool showOnlyLoadingState;
final bool surfaceExecutionStates;
2023-02-01 13:07:35 +00:00
///To show success state even when execution time < debouce time, set this
///flag to true. If the loading state needs to be shown and success state not,
///set the showOnlyLoadingState flag to true, setting this flag to false won't
///help.
final bool alwaysShowSuccessState;
const MenuItemWidget({
2022-09-28 12:58:07 +00:00
required this.captionedTextWidget,
2022-10-22 06:26:47 +00:00
this.isExpandable = false,
this.leadingIcon,
this.leadingIconColor,
this.leadingIconSize = 20.0,
this.leadingIconWidget,
this.trailingIcon,
2022-12-31 18:33:12 +00:00
this.trailingIconColor,
2022-12-06 18:03:39 +00:00
this.trailingWidget,
this.trailingIconIsMuted = false,
this.trailingExtraMargin = 0.0,
this.onTap,
this.onDoubleTap,
2023-04-17 07:13:55 +00:00
this.onLongPress,
2022-09-29 12:21:31 +00:00
this.menuItemColor,
this.alignCaptionedTextToLeft = false,
this.singleBorderRadius = 4.0,
this.multipleBorderRadius = 8.0,
2022-10-21 13:37:57 +00:00
this.pressedColor,
this.expandableController,
this.isBottomBorderRadiusRemoved = false,
this.isTopBorderRadiusRemoved = false,
this.isGestureDetectorDisabled = false,
this.showOnlyLoadingState = false,
this.surfaceExecutionStates = true,
this.alwaysShowSuccessState = false,
Key? key,
}) : super(key: key);
@override
State<MenuItemWidget> createState() => _MenuItemWidgetState();
}
class _MenuItemWidgetState extends State<MenuItemWidget> {
final _debouncer = Debouncer(const Duration(milliseconds: 300));
ValueNotifier<ExecutionState> executionStateNotifier =
ValueNotifier(ExecutionState.idle);
2022-10-21 13:37:57 +00:00
Color? menuItemColor;
late double borderRadius;
@override
void initState() {
2022-10-21 13:37:57 +00:00
menuItemColor = widget.menuItemColor;
borderRadius =
(widget.isBottomBorderRadiusRemoved || widget.isTopBorderRadiusRemoved)
? widget.multipleBorderRadius
: widget.singleBorderRadius;
if (widget.expandableController != null) {
widget.expandableController!.addListener(() {
setState(() {});
});
}
super.initState();
}
2022-10-22 06:13:35 +00:00
@override
void didChangeDependencies() {
menuItemColor = widget.menuItemColor;
super.didChangeDependencies();
}
@override
void didUpdateWidget(covariant MenuItemWidget oldWidget) {
menuItemColor = widget.menuItemColor;
super.didUpdateWidget(oldWidget);
}
@override
void dispose() {
if (widget.expandableController != null) {
widget.expandableController!.dispose();
}
executionStateNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.isExpandable || widget.isGestureDetectorDisabled
? menuItemWidget(context)
: GestureDetector(
onTap: _onTap,
onDoubleTap: widget.onDoubleTap,
2023-04-17 07:13:55 +00:00
onLongPress: widget.onLongPress,
2022-10-21 13:37:57 +00:00
onTapDown: _onTapDown,
onTapUp: _onTapUp,
2022-10-21 14:00:17 +00:00
onTapCancel: _onCancel,
child: menuItemWidget(context),
);
}
Widget menuItemWidget(BuildContext context) {
final circularRadius = Radius.circular(borderRadius);
final isExpanded = widget.expandableController?.value;
final bottomBorderRadius =
(isExpanded != null && isExpanded) || widget.isBottomBorderRadiusRemoved
? const Radius.circular(0)
: circularRadius;
final topBorderRadius = widget.isTopBorderRadiusRemoved
? const Radius.circular(0)
: circularRadius;
return AnimatedContainer(
2022-10-21 13:37:57 +00:00
duration: const Duration(milliseconds: 20),
width: double.infinity,
padding: const EdgeInsets.only(left: 16, right: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: topBorderRadius,
topRight: topBorderRadius,
bottomLeft: bottomBorderRadius,
bottomRight: bottomBorderRadius,
),
2022-10-21 13:37:57 +00:00
color: menuItemColor,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
widget.alignCaptionedTextToLeft && widget.leadingIcon == null
? const SizedBox.shrink()
2023-01-31 12:21:57 +00:00
: LeadingWidget(
leadingIconSize: widget.leadingIconSize,
leadingIcon: widget.leadingIcon,
leadingIconColor: widget.leadingIconColor,
leadingIconWidget: widget.leadingIconWidget,
),
widget.captionedTextWidget,
2023-01-31 12:21:57 +00:00
if (widget.expandableController != null)
ExpansionTrailingIcon(
isExpanded: isExpanded!,
trailingIcon: widget.trailingIcon,
trailingIconColor: widget.trailingIconColor,
)
else
TrailingWidget(
executionStateNotifier: executionStateNotifier,
2023-01-31 12:21:57 +00:00
trailingIcon: widget.trailingIcon,
trailingIconColor: widget.trailingIconColor,
trailingWidget: widget.trailingWidget,
trailingIconIsMuted: widget.trailingIconIsMuted,
trailingExtraMargin: widget.trailingExtraMargin,
showExecutionStates: widget.surfaceExecutionStates,
key: ValueKey(widget.trailingIcon.hashCode),
2023-01-31 12:21:57 +00:00
),
],
),
);
}
2022-10-21 13:37:57 +00:00
Future<void> _onTap() async {
if (executionStateNotifier.value == ExecutionState.inProgress ||
executionStateNotifier.value == ExecutionState.successful) return;
_debouncer.run(
() => Future(
() {
executionStateNotifier.value = ExecutionState.inProgress;
},
),
);
await widget.onTap?.call().then(
(value) {
widget.alwaysShowSuccessState
? executionStateNotifier.value = ExecutionState.successful
: null;
},
onError: (error, stackTrace) => _debouncer.cancelDebounce(),
);
_debouncer.cancelDebounce();
if (widget.alwaysShowSuccessState) {
Future.delayed(const Duration(seconds: 2), () {
executionStateNotifier.value = ExecutionState.idle;
});
return;
}
if (executionStateNotifier.value == ExecutionState.inProgress) {
if (widget.showOnlyLoadingState) {
executionStateNotifier.value = ExecutionState.idle;
} else {
executionStateNotifier.value = ExecutionState.successful;
Future.delayed(const Duration(seconds: 2), () {
executionStateNotifier.value = ExecutionState.idle;
});
}
}
}
2022-10-21 13:37:57 +00:00
void _onTapDown(details) {
if (executionStateNotifier.value == ExecutionState.inProgress ||
executionStateNotifier.value == ExecutionState.successful) return;
2022-10-21 13:37:57 +00:00
setState(() {
if (widget.pressedColor == null) {
hasPassedGestureCallbacks()
? menuItemColor = getEnteColorScheme(context).fillFaintPressed
: menuItemColor = widget.menuItemColor;
} else {
menuItemColor = widget.pressedColor;
}
2022-10-21 13:37:57 +00:00
});
}
bool hasPassedGestureCallbacks() {
2023-04-17 07:13:55 +00:00
return widget.onDoubleTap != null ||
widget.onTap != null ||
widget.onLongPress != null;
}
2022-10-21 13:37:57 +00:00
void _onTapUp(details) {
if (executionStateNotifier.value == ExecutionState.inProgress ||
executionStateNotifier.value == ExecutionState.successful) return;
2022-10-21 13:37:57 +00:00
Future.delayed(
const Duration(milliseconds: 100),
() => setState(() {
menuItemColor = widget.menuItemColor;
}),
);
}
2022-10-21 14:00:17 +00:00
void _onCancel() {
if (executionStateNotifier.value == ExecutionState.inProgress ||
executionStateNotifier.value == ExecutionState.successful) return;
2022-10-21 14:00:17 +00:00
setState(() {
menuItemColor = widget.menuItemColor;
});
}
}