Skip to content

Commit f0f10b0

Browse files
authored
fix: prevent page flickering on rapid sidebar clicks (#8278)
* fix: prevent page flickering on rapid sidebar clicks * refactor: improve deduplication to check both plugin and view IDs
1 parent b5a1ccb commit f0f10b0

File tree

2 files changed

+39
-1
lines changed

2 files changed

+39
-1
lines changed

frontend/appflowy_flutter/lib/workspace/application/tabs/tabs_bloc.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ class TabsBloc extends Bloc<TabsEvent, TabsState> {
2828

2929
late final MenuSharedState menuSharedState;
3030

31+
String? _lastOpenedPluginId;
32+
String? _lastOpenedViewId;
33+
DateTime? _lastOpenTime;
34+
static const _deduplicationWindow = Duration(milliseconds: 500);
35+
3136
@override
3237
Future<void> close() {
3338
state.dispose();
@@ -73,6 +78,22 @@ class TabsBloc extends Bloc<TabsEvent, TabsState> {
7378
_setLatestOpenView(view);
7479
},
7580
openPlugin: (Plugin plugin, ViewPB? view, bool setLatest) {
81+
final now = DateTime.now();
82+
83+
// deduplicate. skip if same plugin and view were just opened
84+
if (_lastOpenedPluginId == plugin.id &&
85+
_lastOpenedViewId == view?.id &&
86+
_lastOpenTime != null) {
87+
final timeSinceLastOpen = now.difference(_lastOpenTime!);
88+
if (timeSinceLastOpen < _deduplicationWindow) {
89+
return;
90+
}
91+
}
92+
93+
_lastOpenedPluginId = plugin.id;
94+
_lastOpenedViewId = view?.id;
95+
_lastOpenTime = now;
96+
7697
state.currentPageManager
7798
..hideSecondaryPlugin()
7899
..setSecondaryPlugin(BlankPagePlugin());

frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,23 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
497497

498498
bool isIconPickerOpened = false;
499499

500+
DateTime? _lastClickTime;
501+
static const _clickThrottleDuration = Duration(milliseconds: 200);
502+
503+
void _handleViewTap() {
504+
final now = DateTime.now();
505+
506+
if (_lastClickTime != null) {
507+
final timeSinceLastClick = now.difference(_lastClickTime!);
508+
if (timeSinceLastClick < _clickThrottleDuration) {
509+
return;
510+
}
511+
}
512+
513+
_lastClickTime = now;
514+
widget.onSelected(context, widget.view);
515+
}
516+
500517
@override
501518
Widget build(BuildContext context) {
502519
bool isSelected = widget.isSelected;
@@ -586,7 +603,7 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
586603

587604
final child = GestureDetector(
588605
behavior: HitTestBehavior.translucent,
589-
onTap: () => widget.onSelected(context, widget.view),
606+
onTap: _handleViewTap,
590607
onTertiaryTapDown: (_) =>
591608
widget.onTertiarySelected?.call(context, widget.view),
592609
child: SizedBox(

0 commit comments

Comments
 (0)