mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
parent
8b04506ac4
commit
2c0cdfa6c5
@ -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(
|
||||
|
@ -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()),
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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()),
|
||||
|
@ -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) =>
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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),
|
||||
);
|
||||
},
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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));
|
||||
|
@ -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()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -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 &&
|
||||
|
@ -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 |
@ -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",
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user