mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-12-27 07:04:08 +00:00
feat: scroll to block after selecting notification item (#6667)
* fix: scrollbar's ScrollController has no ScrollPosition attached * feat: support scrolling to block after selecting notification item * chore: remove debug print * fix: unable to cancel block selection
This commit is contained in:
parent
af6736d352
commit
873ab6cdc7
@ -20,6 +20,7 @@ extension MobileRouter on BuildContext {
|
||||
bool addInRecent = true,
|
||||
bool showMoreButton = true,
|
||||
String? fixedTitle,
|
||||
String? blockId,
|
||||
}) async {
|
||||
// set the current view before pushing the new view
|
||||
getIt<MenuSharedState>().latestOpenView = view;
|
||||
@ -32,6 +33,9 @@ extension MobileRouter on BuildContext {
|
||||
if (fixedTitle != null) {
|
||||
queryParameters[MobileDocumentScreen.viewFixedTitle] = fixedTitle;
|
||||
}
|
||||
if (blockId != null) {
|
||||
queryParameters[MobileDocumentScreen.viewBlockId] = blockId;
|
||||
}
|
||||
}
|
||||
|
||||
final uri = Uri(
|
||||
|
||||
@ -61,6 +61,7 @@ class NotificationReminderBloc
|
||||
reminderContent: node.delta?.toPlainText() ?? '',
|
||||
nodes: [node],
|
||||
status: NotificationReminderStatus.loaded,
|
||||
blockId: reminder.meta[ReminderMetaKeys.blockId],
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -205,6 +206,7 @@ class NotificationReminderState with _$NotificationReminderState {
|
||||
@Default(NotificationReminderStatus.initial)
|
||||
NotificationReminderStatus status,
|
||||
@Default([]) List<Node> nodes,
|
||||
String? blockId,
|
||||
ViewPB? view,
|
||||
}) = _NotificationReminderState;
|
||||
|
||||
|
||||
@ -30,6 +30,7 @@ class MobileViewPage extends StatefulWidget {
|
||||
this.arguments,
|
||||
this.fixedTitle,
|
||||
this.showMoreButton = true,
|
||||
this.blockId,
|
||||
});
|
||||
|
||||
/// view id
|
||||
@ -38,6 +39,7 @@ class MobileViewPage extends StatefulWidget {
|
||||
final String? title;
|
||||
final Map<String, dynamic>? arguments;
|
||||
final bool showMoreButton;
|
||||
final String? blockId;
|
||||
|
||||
// only used in row page
|
||||
final String? fixedTitle;
|
||||
@ -177,6 +179,7 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
context: PluginContext(userProfile: state.userProfilePB),
|
||||
data: {
|
||||
MobileDocumentScreen.viewFixedTitle: widget.fixedTitle,
|
||||
MobileDocumentScreen.viewBlockId: widget.blockId,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@ -143,6 +143,7 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
) ??
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
controller: scrollController,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: child,
|
||||
|
||||
@ -9,6 +9,7 @@ class MobileDocumentScreen extends StatelessWidget {
|
||||
this.title,
|
||||
this.showMoreButton = true,
|
||||
this.fixedTitle,
|
||||
this.blockId,
|
||||
});
|
||||
|
||||
/// view id
|
||||
@ -16,12 +17,14 @@ class MobileDocumentScreen extends StatelessWidget {
|
||||
final String? title;
|
||||
final bool showMoreButton;
|
||||
final String? fixedTitle;
|
||||
final String? blockId;
|
||||
|
||||
static const routeName = '/docs';
|
||||
static const viewId = 'id';
|
||||
static const viewTitle = 'title';
|
||||
static const viewShowMoreButton = 'show_more_button';
|
||||
static const viewFixedTitle = 'fixed_title';
|
||||
static const viewBlockId = 'block_id';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -31,6 +34,7 @@ class MobileDocumentScreen extends StatelessWidget {
|
||||
viewLayout: ViewLayoutPB.Document,
|
||||
showMoreButton: showMoreButton,
|
||||
fixedTitle: fixedTitle,
|
||||
blockId: blockId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,11 +64,15 @@ class NotificationItem extends StatelessWidget {
|
||||
child: child,
|
||||
onTapUp: () async {
|
||||
final view = state.view;
|
||||
final blockId = state.blockId;
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
await context.pushView(view);
|
||||
await context.pushView(
|
||||
view,
|
||||
blockId: blockId,
|
||||
);
|
||||
|
||||
if (!reminder.isRead && context.mounted) {
|
||||
context.read<ReminderBloc>().add(
|
||||
|
||||
@ -128,6 +128,7 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
|
||||
});
|
||||
|
||||
final fixedTitle = data?[MobileDocumentScreen.viewFixedTitle];
|
||||
final blockId = initialBlockId ?? data?[MobileDocumentScreen.viewBlockId];
|
||||
|
||||
return BlocProvider<ViewInfoBloc>.value(
|
||||
value: bloc,
|
||||
@ -137,7 +138,7 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
|
||||
view: view,
|
||||
onDeleted: () => context.onDeleted?.call(view, deletedViewIndex),
|
||||
initialSelection: initialSelection,
|
||||
initialBlockId: initialBlockId,
|
||||
initialBlockId: blockId,
|
||||
fixedTitle: fixedTitle,
|
||||
),
|
||||
),
|
||||
|
||||
@ -46,6 +46,7 @@ class DocumentPage extends StatefulWidget {
|
||||
class _DocumentPageState extends State<DocumentPage>
|
||||
with WidgetsBindingObserver {
|
||||
EditorState? editorState;
|
||||
Selection? initialSelection;
|
||||
late final documentBloc = DocumentBloc(documentId: widget.view.id)
|
||||
..add(const DocumentEvent.initial());
|
||||
|
||||
@ -120,6 +121,9 @@ class _DocumentPageState extends State<DocumentPage>
|
||||
|
||||
final width = context.read<DocumentAppearanceCubit>().state.width;
|
||||
|
||||
// avoid the initial selection calculation change when the editorState is not changed
|
||||
initialSelection ??= _calculateInitialSelection(editorState);
|
||||
|
||||
final Widget child;
|
||||
if (UniversalPlatform.isMobile) {
|
||||
child = BlocBuilder<DocumentPageStyleBloc, DocumentPageStyleState>(
|
||||
@ -133,7 +137,7 @@ class _DocumentPageState extends State<DocumentPage>
|
||||
padding: EditorStyleCustomizer.documentPadding,
|
||||
),
|
||||
header: buildCoverAndIcon(context, state),
|
||||
initialSelection: widget.initialSelection,
|
||||
initialSelection: initialSelection,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@ -151,7 +155,7 @@ class _DocumentPageState extends State<DocumentPage>
|
||||
padding: EditorStyleCustomizer.documentPadding,
|
||||
),
|
||||
header: buildCoverAndIcon(context, state),
|
||||
initialSelection: _calculateInitialSelection(editorState),
|
||||
initialSelection: initialSelection,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -299,6 +303,9 @@ class _DocumentPageState extends State<DocumentPage>
|
||||
final path = _findNodePathByBlockId(editorState, widget.initialBlockId!);
|
||||
if (path != null) {
|
||||
editorState.selectionType = SelectionType.block;
|
||||
editorState.selectionExtraInfo = {
|
||||
selectionExtraInfoDoNotAttachTextService: true,
|
||||
};
|
||||
return Selection.collapsed(
|
||||
Position(
|
||||
path: path,
|
||||
|
||||
@ -181,15 +181,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage>
|
||||
focusManager = AFFocusManager.maybeOf(context);
|
||||
focusManager?.loseFocusNotifier.addListener(_loseFocus);
|
||||
|
||||
final initialSelection = widget.initialSelection;
|
||||
final path = initialSelection?.start.path;
|
||||
if (initialSelection != null && path != null && path.isNotEmpty) {
|
||||
editorScrollController.itemScrollController.jumpTo(
|
||||
index: path.first,
|
||||
alignment: 0.5,
|
||||
);
|
||||
widget.editorState.updateSelectionWithReason(initialSelection);
|
||||
}
|
||||
_scrollToSelectionIfNeeded();
|
||||
|
||||
widget.editorState.service.keyboardService?.registerInterceptor(
|
||||
editorKeyboardInterceptor,
|
||||
@ -197,6 +189,48 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage>
|
||||
});
|
||||
}
|
||||
|
||||
void _scrollToSelectionIfNeeded() {
|
||||
final initialSelection = widget.initialSelection;
|
||||
final path = initialSelection?.start.path;
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// on desktop, using jumpTo to scroll to the selection.
|
||||
// on mobile, using scrollTo to scroll to the selection, because using jumpTo will break the scroll notification metrics.
|
||||
if (UniversalPlatform.isDesktop) {
|
||||
editorScrollController.itemScrollController.jumpTo(
|
||||
index: path.first,
|
||||
alignment: 0.5,
|
||||
);
|
||||
widget.editorState.updateSelectionWithReason(
|
||||
initialSelection,
|
||||
);
|
||||
} else {
|
||||
const delayDuration = Duration(milliseconds: 250);
|
||||
const animationDuration = Duration(milliseconds: 400);
|
||||
Future.delayed(delayDuration, () {
|
||||
editorScrollController.itemScrollController.scrollTo(
|
||||
index: path.first,
|
||||
duration: animationDuration,
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
widget.editorState.updateSelectionWithReason(
|
||||
initialSelection,
|
||||
extraInfo: {
|
||||
selectionExtraInfoDoNotAttachTextService: true,
|
||||
selectionExtraInfoDisableMobileToolbarKey: true,
|
||||
},
|
||||
);
|
||||
}).then((_) {
|
||||
Future.delayed(animationDuration, () {
|
||||
widget.editorState.selectionType = SelectionType.inline;
|
||||
widget.editorState.selectionExtraInfo = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void onSelectionChanged() {
|
||||
if (widget.editorState.isDisposed) {
|
||||
return;
|
||||
|
||||
@ -327,7 +327,10 @@ Future<void> _handleTap(
|
||||
|
||||
if (UniversalPlatform.isMobile) {
|
||||
if (context.mounted && currentViewId != view.id) {
|
||||
await context.pushView(view);
|
||||
await context.pushView(
|
||||
view,
|
||||
blockId: blockId,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
final action = NavigationAction(
|
||||
|
||||
@ -499,6 +499,8 @@ GoRoute _mobileEditorScreenRoute() {
|
||||
);
|
||||
final fixedTitle =
|
||||
state.uri.queryParameters[MobileDocumentScreen.viewFixedTitle];
|
||||
final blockId =
|
||||
state.uri.queryParameters[MobileDocumentScreen.viewBlockId];
|
||||
|
||||
return MaterialExtendedPage(
|
||||
child: MobileDocumentScreen(
|
||||
@ -506,6 +508,7 @@ GoRoute _mobileEditorScreenRoute() {
|
||||
title: title,
|
||||
showMoreButton: showMoreButton ?? true,
|
||||
fixedTitle: fixedTitle,
|
||||
blockId: blockId,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@ -61,8 +61,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: cca5c41
|
||||
resolved-ref: cca5c413480048fbb7dbd4e58f9251e5cf3088e0
|
||||
ref: "4081fb7"
|
||||
resolved-ref: "4081fb756311c6d249f45efbeb59e4cd2a7db530"
|
||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||
source: git
|
||||
version: "4.0.0"
|
||||
|
||||
@ -172,7 +172,7 @@ dependency_overrides:
|
||||
appflowy_editor:
|
||||
git:
|
||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||
ref: "cca5c41"
|
||||
ref: "4081fb7"
|
||||
|
||||
appflowy_editor_plugins:
|
||||
git:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user