fix: sidebar issues (#5444)

* fix: cannot open the view in favorite menu

* fix: pinned docs should also show up in the search list

* fix: the search result doesn't update after deleting query

* fix: remove widget button

* fix: translation

* fix: replace icon selector

* fix: translations

* fix: recent page title sycn slowly

* fix: unable to favorite database on mobile

* fix: auto focus on search textfield when opening favorite menu

* feat: extend the expand icon hit test area

* fix: mobile tab bar dark mode issue

* fix: keep tab views alive

* chore: update frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>

---------

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>
This commit is contained in:
Lucas.Xu 2024-05-31 10:34:23 +08:00 committed by GitHub
parent 8b04506ac4
commit 2c0cdfa6c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 332 additions and 245 deletions

View File

@ -57,7 +57,19 @@ class RecentViewBloc extends Bloc<RecentViewEvent, RecentViewState> {
}
},
);
// only document supports the cover
if (view.layout != ViewLayoutPB.Document) {
emit(
state.copyWith(
name: view.name,
icon: view.icon.value,
),
);
}
final cover = getCoverV2();
if (cover != null) {
emit(
state.copyWith(

View File

@ -11,7 +11,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileFavoriteSpace extends StatelessWidget {
class MobileFavoriteSpace extends StatefulWidget {
const MobileFavoriteSpace({
super.key,
required this.userProfile,
@ -19,8 +19,18 @@ class MobileFavoriteSpace extends StatelessWidget {
final UserProfilePB userProfile;
@override
State<MobileFavoriteSpace> createState() => _MobileFavoriteSpaceState();
}
class _MobileFavoriteSpaceState extends State<MobileFavoriteSpace>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
final workspaceId =
context.read<UserWorkspaceBloc>().state.currentWorkspace?.workspaceId ??
'';
@ -28,7 +38,9 @@ class MobileFavoriteSpace extends StatelessWidget {
providers: [
BlocProvider(
create: (_) => SidebarSectionsBloc()
..add(SidebarSectionsEvent.initial(userProfile, workspaceId)),
..add(
SidebarSectionsEvent.initial(widget.userProfile, workspaceId),
),
),
BlocProvider(
create: (_) => FavoriteBloc()..add(const FavoriteEvent.initial()),

View File

@ -5,13 +5,23 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileHomeSpace extends StatelessWidget {
class MobileHomeSpace extends StatefulWidget {
const MobileHomeSpace({super.key, required this.userProfile});
final UserProfilePB userProfile;
@override
State<MobileHomeSpace> createState() => _MobileHomeSpaceState();
}
class _MobileHomeSpaceState extends State<MobileHomeSpace>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
final workspaceId =
context.read<UserWorkspaceBloc>().state.currentWorkspace?.workspaceId ??
'';
@ -23,7 +33,7 @@ class MobileHomeSpace extends StatelessWidget {
vertical: HomeSpaceViewSizes.mVerticalPadding,
),
child: MobileFolders(
user: userProfile,
user: widget.userProfile,
workspaceId: workspaceId,
showFavorite: false,
),

View File

@ -7,11 +7,21 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileRecentSpace extends StatelessWidget {
class MobileRecentSpace extends StatefulWidget {
const MobileRecentSpace({super.key});
@override
State<MobileRecentSpace> createState() => _MobileRecentSpaceState();
}
class _MobileRecentSpaceState extends State<MobileRecentSpace>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return BlocProvider(
create: (context) =>
RecentViewsBloc()..add(const RecentViewsEvent.initial()),

View File

@ -53,8 +53,8 @@ class MobileViewCard extends StatelessWidget {
return MultiBlocProvider(
providers: [
BlocProvider<ViewBloc>(
create: (context) =>
ViewBloc(view: view)..add(const ViewEvent.initial()),
create: (context) => ViewBloc(view: view, shouldLoadChildViews: false)
..add(const ViewEvent.initial()),
),
BlocProvider(
create: (context) =>

View File

@ -39,6 +39,7 @@ class MobileSpaceTabBar extends StatelessWidget {
indicatorColor: Theme.of(context).primaryColor,
isScrollable: true,
labelStyle: labelStyle,
labelColor: baseStyle?.color,
labelPadding: const EdgeInsets.symmetric(horizontal: 12.0),
unselectedLabelStyle: unselectedLabelStyle,
overlayColor: WidgetStateProperty.all(Colors.transparent),

View File

@ -21,7 +21,7 @@ class MobileBottomNavigationBar extends StatelessWidget {
return Scaffold(
body: navigationShell,
bottomNavigationBar: Theme(
data: ThemeData(
data: Theme.of(context).copyWith(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
),

View File

@ -257,7 +257,6 @@ class _SingleMobileInnerViewItemState extends State<SingleMobileInnerViewItem> {
final children = [
// expand icon
_buildLeftIcon(),
const HSpace(6),
// icon
_buildViewIcon(),
const HSpace(8),
@ -274,11 +273,11 @@ class _SingleMobileInnerViewItemState extends State<SingleMobileInnerViewItem> {
// hover action
// ··· more action button
// children.add(_buildViewMoreActionButton(context));
children.add(_buildViewMoreButton(context));
// only support add button for document layout
if (!widget.isFeedback && widget.view.layout == ViewLayoutPB.Document) {
// + button
children.add(_buildViewMoreButton(context));
children.add(_buildViewAddButton(context));
}
@ -326,22 +325,20 @@ class _SingleMobileInnerViewItemState extends State<SingleMobileInnerViewItem> {
// show > if the view is expandable.
// show · if the view can't contain child views.
Widget _buildLeftIcon() {
if (isReferencedDatabaseView(widget.view, widget.parentView)) {
return const _DotIconWidget();
}
if (context.read<ViewBloc>().state.view.childViews.isEmpty) {
return HSpace(widget.leftPadding);
}
return GestureDetector(
child: AnimatedRotation(
duration: const Duration(milliseconds: 250),
turns: widget.isExpanded ? 0 : -0.25,
child: const FlowySvg(
FlowySvgs.m_expand_s,
blendMode: null,
),
behavior: HitTestBehavior.opaque,
child: Padding(
padding: const EdgeInsets.only(right: 6.0, top: 6.0, bottom: 6.0),
child: FlowySvg(
widget.isExpanded
? FlowySvgs.m_expand_s
: FlowySvgs.m_collapse_s,
blendMode: null,
),
),
onTap: () {
context
@ -431,25 +428,6 @@ class _SingleMobileInnerViewItemState extends State<SingleMobileInnerViewItem> {
}
}
class _DotIconWidget extends StatelessWidget {
const _DotIconWidget();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(6.0),
child: Container(
width: 4,
height: 4,
decoration: BoxDecoration(
color: Theme.of(context).iconTheme.color,
borderRadius: BorderRadius.circular(2),
),
),
);
}
}
// workaround: we should use view.isEndPoint or something to check if the view can contain child views. But currently, we don't have that field.
bool isReferencedDatabaseView(ViewPB view, ViewPB? parentView) {
if (parentView == null) {

View File

@ -1,12 +1,14 @@
import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/plugins/document/application/prelude.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/build_context_extension.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/icon/icon_selector.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart';
import 'package:appflowy/shared/appflowy_network_image.dart';
import 'package:appflowy/shared/flowy_gradient_colors.dart';
import 'package:appflowy/shared/google_fonts_extension.dart';
@ -17,11 +19,12 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:auto_size_text_field/auto_size_text_field.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
double kDocumentCoverHeight = 98.0;
double kDocumentTitlePadding = 20.0;
@ -171,12 +174,40 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
),
),
onTap: () async {
final result = await context.push<EmojiPickerResult>(
MobileEmojiPickerScreen.routeName,
final pageStyleIconBloc = PageStyleIconBloc(view: widget.view)
..add(const PageStyleIconEvent.initial());
await showMobileBottomSheet(
context,
showDragHandle: true,
showDivider: false,
showDoneButton: true,
showHeader: true,
title: LocaleKeys.titleBar_pageIcon.tr(),
backgroundColor: AFThemeExtension.of(context).background,
enableDraggableScrollable: true,
minChildSize: 0.6,
initialChildSize: 0.61,
showRemoveButton: true,
onRemove: () {
pageStyleIconBloc.add(
const PageStyleIconEvent.updateIcon('', true),
);
},
scrollableWidgetBuilder: (_, controller) {
return BlocProvider.value(
value: pageStyleIconBloc,
child: Expanded(
child: Scrollbar(
controller: controller,
child: IconSelector(
scrollController: controller,
),
),
),
);
},
builder: (_) => const SizedBox.shrink(),
);
if (result != null && context.mounted) {
context.read<ViewBloc>().add(ViewEvent.updateIcon(result.emoji));
}
},
);
}

View File

@ -0,0 +1,159 @@
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_search_text_field.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
class IconSelector extends StatefulWidget {
const IconSelector({
super.key,
required this.scrollController,
});
final ScrollController scrollController;
@override
State<IconSelector> createState() => _IconSelectorState();
}
class _IconSelectorState extends State<IconSelector> {
EmojiData? emojiData;
List<String> availableEmojis = [];
PageStyleIconBloc? pageStyleIconBloc;
@override
void initState() {
super.initState();
// load the emoji data from cache if it's available
if (kCachedEmojiData != null) {
emojiData = kCachedEmojiData;
availableEmojis = _setupAvailableEmojis(emojiData!);
} else {
EmojiData.builtIn().then(
(value) {
kCachedEmojiData = value;
setState(() {
emojiData = value;
availableEmojis = _setupAvailableEmojis(value);
});
},
);
}
pageStyleIconBloc = context.read<PageStyleIconBloc>();
}
@override
void dispose() {
pageStyleIconBloc?.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (emojiData == null) {
return const Center(child: CircularProgressIndicator());
}
return RepaintBoundary(
child: BlocBuilder<PageStyleIconBloc, PageStyleIconState>(
builder: (_, state) => Column(
children: [
_buildSearchBar(context),
Expanded(
child: GridView.count(
crossAxisCount: 7,
controller: widget.scrollController,
children: [
for (final emoji in availableEmojis)
_buildEmoji(context, emoji, state.icon),
],
),
),
],
),
),
);
}
Widget _buildEmoji(
BuildContext context,
String emoji,
String? selectedEmoji,
) {
Widget child = SizedBox.square(
dimension: 24.0,
child: Center(
child: FlowyText.emoji(
emoji,
fontSize: 24,
),
),
);
if (emoji == selectedEmoji) {
child = Center(
child: Container(
width: 40,
height: 40,
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 1.40,
strokeAlign: BorderSide.strokeAlignOutside,
color: Color(0xFF00BCF0),
),
borderRadius: BorderRadius.circular(10),
),
),
child: child,
),
);
}
return GestureDetector(
onTap: () {
context.read<PageStyleIconBloc>().add(
PageStyleIconEvent.updateIcon(emoji, true),
);
},
child: child,
);
}
List<String> _setupAvailableEmojis(EmojiData emojiData) {
final categories = emojiData.categories;
availableEmojis = categories
.map((e) => e.emojiIds.map((e) => emojiData.getEmojiById(e)))
.expand((e) => e)
.toList();
return availableEmojis;
}
Widget _buildSearchBar(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 12.0,
),
child: FlowyMobileSearchTextField(
onChanged: (keyword) {
if (emojiData == null) {
return;
}
final filtered = emojiData!.filterByKeyword(keyword);
final availableEmojis = _setupAvailableEmojis(filtered);
setState(() {
this.availableEmojis = availableEmojis;
});
},
),
);
}
}

View File

@ -1,8 +1,7 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_search_text_field.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/icon/icon_selector.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_util.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
@ -11,7 +10,6 @@ import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
import 'package:go_router/go_router.dart';
class PageStyleIcon extends StatefulWidget {
@ -94,7 +92,7 @@ class _PageStyleIconState extends State<PageStyleIcon> {
child: Expanded(
child: Scrollbar(
controller: controller,
child: _IconSelector(
child: IconSelector(
scrollController: controller,
),
),
@ -105,154 +103,3 @@ class _PageStyleIconState extends State<PageStyleIcon> {
);
}
}
class _IconSelector extends StatefulWidget {
const _IconSelector({
required this.scrollController,
});
final ScrollController scrollController;
@override
State<_IconSelector> createState() => _IconSelectorState();
}
class _IconSelectorState extends State<_IconSelector> {
EmojiData? emojiData;
List<String> availableEmojis = [];
PageStyleIconBloc? pageStyleIconBloc;
@override
void initState() {
super.initState();
// load the emoji data from cache if it's available
if (kCachedEmojiData != null) {
emojiData = kCachedEmojiData;
availableEmojis = _setupAvailableEmojis(emojiData!);
} else {
EmojiData.builtIn().then(
(value) {
kCachedEmojiData = value;
setState(() {
emojiData = value;
availableEmojis = _setupAvailableEmojis(value);
});
},
);
}
pageStyleIconBloc = context.read<PageStyleIconBloc>();
}
@override
void dispose() {
pageStyleIconBloc?.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (emojiData == null) {
return const Center(child: CircularProgressIndicator());
}
return RepaintBoundary(
child: BlocBuilder<PageStyleIconBloc, PageStyleIconState>(
builder: (_, state) => Column(
children: [
_buildSearchBar(context),
Expanded(
child: GridView.count(
crossAxisCount: 7,
controller: widget.scrollController,
children: [
for (final emoji in availableEmojis)
_buildEmoji(context, emoji, state.icon),
],
),
),
],
),
),
);
}
Widget _buildEmoji(
BuildContext context,
String emoji,
String? selectedEmoji,
) {
Widget child = SizedBox.square(
dimension: 24.0,
child: Center(
child: FlowyText.emoji(
emoji,
fontSize: 24,
),
),
);
if (emoji == selectedEmoji) {
child = Center(
child: Container(
width: 40,
height: 40,
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 1.40,
strokeAlign: BorderSide.strokeAlignOutside,
color: Color(0xFF00BCF0),
),
borderRadius: BorderRadius.circular(10),
),
),
child: child,
),
);
}
return GestureDetector(
onTap: () {
context.read<PageStyleIconBloc>().add(
PageStyleIconEvent.updateIcon(emoji, true),
);
},
child: child,
);
}
List<String> _setupAvailableEmojis(EmojiData emojiData) {
final categories = emojiData.categories;
availableEmojis = categories
.map((e) => e.emojiIds.map((e) => emojiData.getEmojiById(e)))
.expand((e) => e)
.toList();
return availableEmojis;
}
Widget _buildSearchBar(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 12.0,
),
child: FlowyMobileSearchTextField(
onChanged: (keyword) {
if (emojiData == null) {
return;
}
final filtered = emojiData!.filterByKeyword(keyword);
final availableEmojis = _setupAvailableEmojis(filtered);
setState(() {
this.availableEmojis = availableEmojis;
});
},
),
);
}
}

View File

@ -19,7 +19,7 @@ import 'package:protobuf/protobuf.dart';
part 'view_bloc.freezed.dart';
class ViewBloc extends Bloc<ViewEvent, ViewState> {
ViewBloc({required this.view})
ViewBloc({required this.view, this.shouldLoadChildViews = true})
: viewBackendSvc = ViewBackendService(),
listener = ViewListener(viewId: view.id),
favoriteListener = FavoriteListener(),
@ -31,6 +31,7 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
final ViewBackendService viewBackendSvc;
final ViewListener listener;
final FavoriteListener favoriteListener;
final bool shouldLoadChildViews;
@override
Future<void> close() async {
@ -74,8 +75,10 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
},
);
final isExpanded = await _getViewIsExpanded(view);
emit(state.copyWith(isExpanded: isExpanded));
await _loadChildViews(emit);
emit(state.copyWith(isExpanded: isExpanded, view: view));
if (shouldLoadChildViews) {
await _loadChildViews(emit);
}
},
setIsEditing: (e) {
emit(state.copyWith(isEditing: e.isEditing));

View File

@ -109,6 +109,7 @@ class _FavoriteFolderState extends State<FavoriteFolder> {
const HSpace(4.0),
],
shouldRenderChildren: false,
shouldLoadChildViews: false,
onTertiarySelected: (_, view) =>
context.read<TabsBloc>().openTab(view),
onSelected: (_, view) {
@ -152,6 +153,7 @@ class FavoriteMoreButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final favoriteBloc = context.watch<FavoriteBloc>();
final tabsBloc = context.read<TabsBloc>();
final unpinnedViews = favoriteBloc.state.unpinnedViews;
// only show the more button if there are unpinned views
if (unpinnedViews.isEmpty) {
@ -169,8 +171,11 @@ class FavoriteMoreButton extends StatelessWidget {
borderRadius: 10.0,
),
popupBuilder: (_) {
return BlocProvider.value(
value: favoriteBloc,
return MultiBlocProvider(
providers: [
BlocProvider.value(value: favoriteBloc),
BlocProvider.value(value: tabsBloc),
],
child: const FavoriteMenu(minWidth: minWidth),
);
},

View File

@ -1,11 +1,13 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_menu_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_more_actions.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_pin_action.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/cupertino.dart';
@ -125,7 +127,10 @@ class FavoriteMenu extends StatelessWidget {
view: e,
spaceType: FolderSpaceType.favorite,
level: 0,
onSelected: (view, _) {},
onSelected: (_, view) {
context.read<TabsBloc>().openPlugin(view);
PopoverContainer.maybeOf(context)?.close();
},
isFeedback: false,
isDraggable: false,
shouldRenderChildren: false,
@ -143,7 +148,7 @@ class FavoriteMenu extends StatelessWidget {
}
}
class _FavoriteSearchField extends StatelessWidget {
class _FavoriteSearchField extends StatefulWidget {
const _FavoriteSearchField({
required this.width,
required this.onSearch,
@ -152,11 +157,30 @@ class _FavoriteSearchField extends StatelessWidget {
final double width;
final void Function(BuildContext context, String text) onSearch;
@override
State<_FavoriteSearchField> createState() => _FavoriteSearchFieldState();
}
class _FavoriteSearchFieldState extends State<_FavoriteSearchField> {
final focusNode = FocusNode();
@override
void initState() {
super.initState();
focusNode.requestFocus();
}
@override
void dispose() {
focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
height: 30,
width: width,
width: widget.width,
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
@ -169,8 +193,9 @@ class _FavoriteSearchField extends StatelessWidget {
),
),
child: CupertinoSearchTextField(
onChanged: (text) => onSearch(context, text),
onChanged: (text) => widget.onSearch(context, text),
padding: EdgeInsets.zero,
focusNode: focusNode,
placeholder: LocaleKeys.search_label.tr(),
prefixIcon: const FlowySvg(FlowySvgs.m_search_m),
prefixInsets: const EdgeInsets.only(left: 12.0, right: 8.0),

View File

@ -1,5 +1,4 @@
import 'package:appflowy/workspace/application/favorite/favorite_service.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -43,9 +42,9 @@ class FavoriteMenuBloc extends Bloc<FavoriteMenuEvent, FavoriteMenuState> {
if (query.isNotEmpty) {
queriedViews = _filter(views, query);
todayViews = _filter(state.todayViews, query);
thisWeekViews = _filter(state.thisWeekViews, query);
otherViews = _filter(state.otherViews, query);
todayViews = _filter(todayViews, query);
thisWeekViews = _filter(thisWeekViews, query);
otherViews = _filter(otherViews, query);
}
emit(
@ -66,28 +65,20 @@ class FavoriteMenuBloc extends Bloc<FavoriteMenuEvent, FavoriteMenuState> {
final FavoriteService _service = FavoriteService();
RepeatedFavoriteViewPB? _source;
List<ViewPB> _filter(List<ViewPB> views, String query) {
return views
.where(
(view) => view.name.toLowerCase().contains(query.toLowerCase()),
)
.toList();
}
List<ViewPB> _filter(List<ViewPB> views, String query) => views
.where((view) => view.name.toLowerCase().contains(query.toLowerCase()))
.toList();
// all, today, last week, other
(List<ViewPB>, List<ViewPB>, List<ViewPB>, List<ViewPB>) _getViews(
RepeatedFavoriteViewPB source,
) {
final List<ViewPB> views =
source.items.map((v) => v.item).where((e) => !e.isPinned).toList();
final List<ViewPB> views = source.items.map((v) => v.item).toList();
final List<ViewPB> todayViews = [];
final List<ViewPB> thisWeekViews = [];
final List<ViewPB> otherViews = [];
for (final favoriteView in source.items) {
final view = favoriteView.item;
if (view.isPinned) {
continue;
}
final date = DateTime.fromMillisecondsSinceEpoch(
favoriteView.timestamp.toInt() * 1000,
);

View File

@ -3,7 +3,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
@ -31,8 +30,6 @@ class FavoritePinAction extends StatelessWidget {
width: 24,
icon: icon,
onPressed: () {
PopoverContainer.maybeOf(context)?.closeAll();
view.isPinned
? context.read<FavoriteBloc>().add(FavoriteEvent.unpin(view))
: context.read<FavoriteBloc>().add(FavoriteEvent.pin(view));

View File

@ -13,11 +13,12 @@ class SidebarFooter extends StatelessWidget {
return const Row(
children: [
Expanded(child: SidebarTrashButton()),
SizedBox(
height: 16,
child: VerticalDivider(width: 1, color: Color(0x141F2329)),
),
Expanded(child: SidebarWidgetButton()),
// Enable it when the widget button is ready
// SizedBox(
// height: 16,
// child: VerticalDivider(width: 1, color: Color(0x141F2329)),
// ),
// Expanded(child: SidebarWidgetButton()),
],
);
}

View File

@ -57,6 +57,7 @@ class ViewItem extends StatelessWidget {
this.shouldRenderChildren = true,
this.leftIconBuilder,
this.rightIconsBuilder,
this.shouldLoadChildViews = true,
});
final ViewPB view;
@ -107,10 +108,14 @@ class ViewItem extends StatelessWidget {
// custom the right icon widget, if it's null, the default ... and + button will be used
final ViewItemRightIconsBuilder? rightIconsBuilder;
final bool shouldLoadChildViews;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => ViewBloc(view: view)..add(const ViewEvent.initial()),
create: (_) =>
ViewBloc(view: view, shouldLoadChildViews: shouldLoadChildViews)
..add(const ViewEvent.initial()),
child: BlocConsumer<ViewBloc, ViewState>(
listenWhen: (p, c) =>
c.lastCreatedView != null &&

View File

@ -1,3 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.96667 5.59993C9.23333 5.79993 9.23333 6.19993 8.96667 6.39993L4.3 9.89993C3.97038 10.1471 3.5 9.91195 3.5 9.49993L3.5 2.49993C3.5 2.08791 3.97038 1.85272 4.3 2.09993L8.96667 5.59993Z" fill="#8F959E"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.9667 7.59993C11.2333 7.79993 11.2333 8.19993 10.9667 8.39993L6.3 11.8999C5.97038 12.1471 5.5 11.912 5.5 11.4999L5.5 4.49993C5.5 4.08791 5.97038 3.85272 6.3 4.09993L10.9667 7.59993Z" fill="#8F959E"/>
</svg>

Before

Width:  |  Height:  |  Size: 316 B

After

Width:  |  Height:  |  Size: 315 B

View File

@ -201,8 +201,8 @@
"addBlockBelow": "Add a block below"
},
"sideBar": {
"closeSidebar": "Close side bar",
"openSidebar": "Open side bar",
"closeSidebar": "Close sidebar",
"openSidebar": "Open sidebar",
"personal": "Personal",
"favorites": "Favorites",
"clickToHidePersonal": "Click to hide personal section",

View File

@ -139,7 +139,7 @@
"addToFavorites": "Add to Favorites",
"copyLink": "Copy Link",
"changeIcon": "Change icon",
"collapseAllPages": "Collapse all pages"
"collapseAllPages": "Collapse all subpages"
},
"blankPageTitle": "Blank page",
"newPageText": "New page",
@ -229,8 +229,8 @@
"genSummary": "Generate summary"
},
"sideBar": {
"closeSidebar": "Close side bar",
"openSidebar": "Open side bar",
"closeSidebar": "Close sidebar",
"openSidebar": "Open sidebar",
"personal": "Personal",
"private": "Private",
"workspace": "Workspace",