mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-09-26 00:40:13 +00:00
fix: some mobile search LR issues (#7836)
This commit is contained in:
parent
1f54946a0e
commit
15bfdfb550
@ -36,7 +36,7 @@ void main() {
|
||||
expect(find.byType(MobileSearchRecentList), findsNothing);
|
||||
expect(find.byType(MobileSearchResultList), findsOneWidget);
|
||||
expect(
|
||||
find.text(LocaleKeys.search_noResultForSearching.tr(args: [query])),
|
||||
find.text(LocaleKeys.search_noResultForSearching.tr()),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
|
@ -47,9 +47,10 @@ class NotificationReminderBloc
|
||||
status: NotificationReminderStatus.error,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final layout = view!.layout;
|
||||
final layout = view.layout;
|
||||
|
||||
if (layout.isDocumentView) {
|
||||
final node = await _getContent(reminder);
|
||||
|
@ -163,6 +163,7 @@ class _MobileBottomSheetEditLinkWidgetState
|
||||
decoration: LinkStyle.buildLinkTextFieldInputDecoration(
|
||||
LocaleKeys.document_toolbar_linkNameHint.tr(),
|
||||
contentPadding: EdgeInsets.all(14),
|
||||
radius: 12,
|
||||
context,
|
||||
),
|
||||
),
|
||||
|
@ -1,12 +1,9 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/command_palette/command_palette_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/command_palette/search_result_ext.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-search/result.pb.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -28,7 +25,7 @@ class MobileSearchResultCell extends StatelessWidget {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
buildIcon(),
|
||||
buildIcon(theme),
|
||||
HSpace(12),
|
||||
Flexible(
|
||||
child: Column(
|
||||
@ -57,36 +54,17 @@ class MobileSearchResultCell extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildIcon() {
|
||||
Widget buildIcon(AppFlowyThemeData theme) {
|
||||
final icon = item.icon;
|
||||
if (icon.ty == ResultIconTypePB.Emoji) {
|
||||
return icon.getIcon(size: 16.0, lineHeight: 20 / 16) ?? SizedBox.shrink();
|
||||
} else {
|
||||
return icon.getIcon(size: 20) ?? SizedBox.shrink();
|
||||
} else {
|
||||
return icon.getIcon(size: 20, iconColor: theme.iconColorScheme.primary) ??
|
||||
SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildPath(CommandPaletteState state, AppFlowyThemeData theme) {
|
||||
bool isInTrash = false;
|
||||
for (final view in state.trash) {
|
||||
if (view.id == item.id) {
|
||||
isInTrash = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isInTrash) {
|
||||
return Row(
|
||||
children: [
|
||||
const FlowySvg(FlowySvgs.trash_s, size: Size.square(20)),
|
||||
const HSpace(4.0),
|
||||
Text(
|
||||
'${LocaleKeys.trash_text.tr()} / ${item.displayName}',
|
||||
style: theme.textStyle.body
|
||||
.standard(color: theme.textColorScheme.secondary),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return BlocProvider(
|
||||
create: (context) => ViewAncestorBloc(item.id),
|
||||
child: BlocBuilder<ViewAncestorBloc, ViewAncestorState>(
|
||||
@ -105,7 +83,6 @@ class MobileSearchResultCell extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildSummary(AppFlowyThemeData theme) {
|
||||
if (item.content.isEmpty) {
|
||||
|
@ -61,24 +61,19 @@ class MobileSearchScreen extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
bottom: false,
|
||||
child: Provider.value(
|
||||
return Provider.value(
|
||||
value: userProfile,
|
||||
child: MobileSearchPage(
|
||||
userProfile: userProfile,
|
||||
workspaceLatestPB: latest,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MobileSearchPage extends StatelessWidget {
|
||||
class MobileSearchPage extends StatefulWidget {
|
||||
const MobileSearchPage({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
@ -88,32 +83,63 @@ class MobileSearchPage extends StatelessWidget {
|
||||
final UserProfilePB userProfile;
|
||||
final WorkspaceLatestPB workspaceLatestPB;
|
||||
|
||||
@override
|
||||
State<MobileSearchPage> createState() => _MobileSearchPageState();
|
||||
}
|
||||
|
||||
class _MobileSearchPageState extends State<MobileSearchPage> {
|
||||
bool get enableShowAISearch =>
|
||||
userProfile.workspaceType == WorkspaceTypePB.ServerW;
|
||||
widget.userProfile.workspaceType == WorkspaceTypePB.ServerW;
|
||||
|
||||
final focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<CommandPaletteBloc, CommandPaletteState>(
|
||||
builder: (context, state) {
|
||||
return Padding(
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MobileSearchTextfield(
|
||||
focusNode: focusNode,
|
||||
hintText: enableShowAISearch
|
||||
? LocaleKeys.search_searchOrAskAI.tr()
|
||||
: LocaleKeys.search_label.tr(),
|
||||
query: state.query ?? '',
|
||||
onChanged: (value) => context
|
||||
.read<CommandPaletteBloc>()
|
||||
.add(CommandPaletteEvent.searchChanged(search: value)),
|
||||
onChanged: (value) =>
|
||||
context.read<CommandPaletteBloc>().add(
|
||||
CommandPaletteEvent.searchChanged(search: value),
|
||||
),
|
||||
),
|
||||
if (enableShowAISearch)
|
||||
MobileSearchAskAiEntrance(query: state.query),
|
||||
Flexible(child: SafeArea(child: MobileSearchResult())),
|
||||
Flexible(
|
||||
child: NotificationListener(
|
||||
child: MobileSearchResult(),
|
||||
onNotification: (t) {
|
||||
if (t is ScrollUpdateNotification) {
|
||||
if (focusNode.hasFocus) {
|
||||
focusNode.unfocus();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -6,6 +6,7 @@ import 'package:appflowy/workspace/application/command_palette/command_palette_b
|
||||
import 'package:appflowy/workspace/application/command_palette/search_result_list_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
@ -36,13 +37,18 @@ class MobileSearchRecentList extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
final commandPaletteState = context.read<CommandPaletteBloc>().state;
|
||||
|
||||
final trashIdSet = commandPaletteState.trash.map((e) => e.id).toSet();
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
RecentViewsBloc()..add(const RecentViewsEvent.initial()),
|
||||
child: BlocBuilder<RecentViewsBloc, RecentViewsState>(
|
||||
builder: (context, state) {
|
||||
final List<ViewPB> recentViews =
|
||||
state.views.map((e) => e.item).toSet().toList();
|
||||
final List<ViewPB> recentViews = state.views
|
||||
.map((e) => e.item)
|
||||
.where((e) => !trashIdSet.contains(e.id))
|
||||
.toList();
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -115,7 +121,7 @@ class _MobileSearchResultListState extends State<MobileSearchResultList> {
|
||||
children: [
|
||||
const VSpace(16),
|
||||
Text(
|
||||
LocaleKeys.search_bestMatch.tr(),
|
||||
LocaleKeys.commandPalette_bestMatches.tr(),
|
||||
style: theme.textStyle.body
|
||||
.enhanced(color: theme.textColorScheme.secondary),
|
||||
),
|
||||
@ -130,6 +136,10 @@ class _MobileSearchResultListState extends State<MobileSearchResultList> {
|
||||
.fold((s) => s, (s) => null);
|
||||
if (view != null && context.mounted) {
|
||||
await _goToView(context, view);
|
||||
} else {
|
||||
Log.error(
|
||||
'tapping search result, view not found: ${item.id}',
|
||||
);
|
||||
}
|
||||
},
|
||||
child: MobileSearchResultCell(
|
||||
@ -161,12 +171,13 @@ class _MobileSearchResultListState extends State<MobileSearchResultList> {
|
||||
),
|
||||
const VSpace(12),
|
||||
Text(
|
||||
LocaleKeys.search_noResultForSearching.tr(args: [query]),
|
||||
LocaleKeys.search_noResultForSearching.tr(),
|
||||
style: theme.textStyle.body.enhanced(color: textColor),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
textAlign: TextAlign.center,
|
||||
LocaleKeys.search_noResultForSearchingHint.tr(),
|
||||
style: theme.textStyle.caption.standard(color: textColor),
|
||||
),
|
||||
|
@ -11,11 +11,13 @@ class MobileSearchTextfield extends StatefulWidget {
|
||||
this.onChanged,
|
||||
required this.hintText,
|
||||
required this.query,
|
||||
required this.focusNode,
|
||||
});
|
||||
|
||||
final String hintText;
|
||||
final String query;
|
||||
final ValueChanged<String>? onChanged;
|
||||
final FocusNode focusNode;
|
||||
|
||||
@override
|
||||
State<MobileSearchTextfield> createState() => _MobileSearchTextfieldState();
|
||||
@ -23,18 +25,15 @@ class MobileSearchTextfield extends StatefulWidget {
|
||||
|
||||
class _MobileSearchTextfieldState extends State<MobileSearchTextfield> {
|
||||
late final TextEditingController controller;
|
||||
late final FocusNode focusNode;
|
||||
final ValueNotifier<bool> hasFocusValueNotifier = ValueNotifier(true);
|
||||
|
||||
FocusNode get focusNode => widget.focusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = TextEditingController(text: widget.query);
|
||||
focusNode = FocusNode();
|
||||
focusNode.addListener(() {
|
||||
if (!mounted) return;
|
||||
hasFocusValueNotifier.value = focusNode.hasFocus;
|
||||
});
|
||||
focusNode.addListener(onFocusChanged);
|
||||
controller.addListener(() {
|
||||
if (!mounted) return;
|
||||
widget.onChanged?.call(controller.text);
|
||||
@ -46,7 +45,7 @@ class _MobileSearchTextfieldState extends State<MobileSearchTextfield> {
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
focusNode.dispose();
|
||||
focusNode.removeListener(onFocusChanged);
|
||||
hasFocusValueNotifier.dispose();
|
||||
bottomNavigationBarItemType.removeListener(onBackOrLeave);
|
||||
super.dispose();
|
||||
@ -157,11 +156,17 @@ class _MobileSearchTextfieldState extends State<MobileSearchTextfield> {
|
||||
);
|
||||
}
|
||||
|
||||
void onFocusChanged() {
|
||||
if (!mounted) return;
|
||||
hasFocusValueNotifier.value = focusNode.hasFocus;
|
||||
}
|
||||
|
||||
void onBackOrLeave() {
|
||||
final label = bottomNavigationBarItemType.value;
|
||||
if (label == BottomNavigationBarItemType.search.label) {
|
||||
focusNode.requestFocus();
|
||||
} else {
|
||||
focusNode.unfocus();
|
||||
controller.clear();
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,11 @@ class LinkStyle {
|
||||
BuildContext context, {
|
||||
bool showErrorBorder = false,
|
||||
EdgeInsets? contentPadding,
|
||||
double? radius,
|
||||
}) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
final border = OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.0)),
|
||||
borderRadius: BorderRadius.all(Radius.circular(radius ?? 8.0)),
|
||||
borderSide: BorderSide(color: borderColor(context)),
|
||||
);
|
||||
final enableBorder = border.copyWith(
|
||||
|
@ -5,7 +5,11 @@ import 'package:appflowy_backend/protobuf/flowy-search/result.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
extension GetIcon on ResultIconPB {
|
||||
Widget? getIcon({double size = 18.0, double lineHeight = 1.0}) {
|
||||
Widget? getIcon({
|
||||
double size = 18.0,
|
||||
double lineHeight = 1.0,
|
||||
Color? iconColor,
|
||||
}) {
|
||||
final iconValue = value, iconType = ty;
|
||||
if (iconType == ResultIconTypePB.Emoji) {
|
||||
return iconValue.isNotEmpty
|
||||
@ -17,7 +21,11 @@ extension GetIcon on ResultIconPB {
|
||||
: null;
|
||||
} else if (ty == ResultIconTypePB.Icon) {
|
||||
if (_resultIconValueTypes.contains(iconValue)) {
|
||||
return FlowySvg(getViewSvg(), size: Size.square(size));
|
||||
return FlowySvg(
|
||||
getViewSvg(),
|
||||
size: Size.square(size),
|
||||
color: iconColor,
|
||||
);
|
||||
}
|
||||
return RawEmojiIconWidget(
|
||||
emoji: EmojiIconData(iconType.toFlowyIconType(), iconValue),
|
||||
|
@ -2347,8 +2347,8 @@
|
||||
"searchOrAskAI": "Search or ask AI",
|
||||
"askAIAnything": "Ask AI anything",
|
||||
"askAIFor": "Ask AI",
|
||||
"noResultForSearching": "No result for \"{}\"",
|
||||
"noResultForSearchingHint": "Some results may be in your deleted pages",
|
||||
"noResultForSearching": "No matches found",
|
||||
"noResultForSearchingHint": "Try different questions or keywords.\n Some pages may be in the Trash.",
|
||||
"bestMatch": "Best match",
|
||||
"placeholder": {
|
||||
"actions": "Search actions..."
|
||||
|
Loading…
x
Reference in New Issue
Block a user