diff --git a/frontend/appflowy_flutter/lib/workspace/application/tabs/tabs_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/tabs/tabs_bloc.dart index f27539cddde95..93543f61bc196 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/tabs/tabs_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/tabs/tabs_bloc.dart @@ -28,6 +28,11 @@ class TabsBloc extends Bloc { late final MenuSharedState menuSharedState; + String? _lastOpenedPluginId; + String? _lastOpenedViewId; + DateTime? _lastOpenTime; + static const _deduplicationWindow = Duration(milliseconds: 500); + @override Future close() { state.dispose(); @@ -73,6 +78,22 @@ class TabsBloc extends Bloc { _setLatestOpenView(view); }, openPlugin: (Plugin plugin, ViewPB? view, bool setLatest) { + final now = DateTime.now(); + + // deduplicate. skip if same plugin and view were just opened + if (_lastOpenedPluginId == plugin.id && + _lastOpenedViewId == view?.id && + _lastOpenTime != null) { + final timeSinceLastOpen = now.difference(_lastOpenTime!); + if (timeSinceLastOpen < _deduplicationWindow) { + return; + } + } + + _lastOpenedPluginId = plugin.id; + _lastOpenedViewId = view?.id; + _lastOpenTime = now; + state.currentPageManager ..hideSecondaryPlugin() ..setSecondaryPlugin(BlankPagePlugin()); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart index ff4c78797d193..8f1c3efca8078 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart @@ -497,6 +497,23 @@ class _SingleInnerViewItemState extends State { bool isIconPickerOpened = false; + DateTime? _lastClickTime; + static const _clickThrottleDuration = Duration(milliseconds: 200); + + void _handleViewTap() { + final now = DateTime.now(); + + if (_lastClickTime != null) { + final timeSinceLastClick = now.difference(_lastClickTime!); + if (timeSinceLastClick < _clickThrottleDuration) { + return; + } + } + + _lastClickTime = now; + widget.onSelected(context, widget.view); + } + @override Widget build(BuildContext context) { bool isSelected = widget.isSelected; @@ -586,7 +603,7 @@ class _SingleInnerViewItemState extends State { final child = GestureDetector( behavior: HitTestBehavior.translucent, - onTap: () => widget.onSelected(context, widget.view), + onTap: _handleViewTap, onTertiaryTapDown: (_) => widget.onTertiarySelected?.call(context, widget.view), child: SizedBox(