mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: editor page issues (#4055)
* fix: disable editor in card detail page * fix: mobile toolbar disappears after editing link * fix: favorite icon shows incorrectly * fix: inkWell when pressing on the Trash is different from the rest of our list tiles in the app * fix: recent view didn't update after deleting view * fix: trash button too small * feat: use new bottom sheet style in cover plugin * feat: use new bottom sheet style in add new page * feat: use new bottom sheet style in view page option * feat: use new bottom sheet style in image block * feat: use new bottom sheet style in settings block and edit link menu * fix: data picker doesn't show * fix: flutter analyze
This commit is contained in:
parent
afab3d5374
commit
0683483fd4
@ -2,28 +2,21 @@ import 'package:appflowy/mobile/presentation/database/board/mobile_board_screen.
|
||||
import 'package:appflowy/mobile/presentation/database/mobile_calendar_screen.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/mobile_grid_screen.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy/workspace/application/recent/recent_service.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class MobileRouterRecord {
|
||||
PropertyValueNotifier<String> lastPushedRouter =
|
||||
PropertyValueNotifier<String>('');
|
||||
}
|
||||
|
||||
extension MobileRouter on BuildContext {
|
||||
Future<void> pushView(ViewPB view) async {
|
||||
await FolderEventSetLatestView(ViewIdPB(value: view.id)).send();
|
||||
getIt<MobileRouterRecord>().lastPushedRouter.value = view.routeName;
|
||||
push(
|
||||
Uri(
|
||||
path: view.routeName,
|
||||
queryParameters: view.queryParameters,
|
||||
).toString(),
|
||||
);
|
||||
).then((value) {
|
||||
RecentService().updateRecentViews([view.id], true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,7 +149,8 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
return AppBarMoreButton(
|
||||
onTap: (context) {
|
||||
showMobileBottomSheet(
|
||||
context: context,
|
||||
context,
|
||||
showDragHandle: true,
|
||||
builder: (_) => _buildViewPageBottomSheet(context),
|
||||
);
|
||||
},
|
||||
|
@ -2,6 +2,7 @@ export 'bottom_sheet_action_widget.dart';
|
||||
export 'bottom_sheet_add_new_page.dart';
|
||||
export 'bottom_sheet_drag_handler.dart';
|
||||
export 'bottom_sheet_rename_widget.dart';
|
||||
export 'bottom_sheet_view_item.dart';
|
||||
export 'bottom_sheet_view_item_body.dart';
|
||||
export 'bottom_sheet_view_item_header.dart';
|
||||
export 'bottom_sheet_view_page.dart';
|
||||
|
@ -52,50 +52,44 @@ class _MobileBottomSheetEditLinkWidgetState
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildTextField(textController, null),
|
||||
const VSpace(12.0),
|
||||
_buildTextField(hrefController, LocaleKeys.editor_linkTextHint.tr()),
|
||||
const VSpace(12.0),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
text: LocaleKeys.button_cancel.tr(),
|
||||
onTap: () => context.pop(),
|
||||
),
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildTextField(textController, null),
|
||||
const VSpace(12.0),
|
||||
_buildTextField(hrefController, LocaleKeys.editor_linkTextHint.tr()),
|
||||
const VSpace(12.0),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
text: LocaleKeys.button_cancel.tr(),
|
||||
onTap: () => context.pop(),
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
text: LocaleKeys.button_done.tr(),
|
||||
onTap: () {
|
||||
widget.onEdit(textController.text, hrefController.text);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (widget.href != null && isURL(widget.href)) ...[
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
text: LocaleKeys.button_done.tr(),
|
||||
text: LocaleKeys.editor_openLink.tr(),
|
||||
onTap: () {
|
||||
widget.onEdit(textController.text, hrefController.text);
|
||||
safeLaunchUrl(widget.href!);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (widget.href != null && isURL(widget.href)) ...[
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
text: LocaleKeys.editor_openLink.tr(),
|
||||
onTap: () {
|
||||
safeLaunchUrl(widget.href!);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,118 @@
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
enum MobileBottomSheetType {
|
||||
view,
|
||||
rename,
|
||||
}
|
||||
|
||||
class MobileViewItemBottomSheet extends StatefulWidget {
|
||||
const MobileViewItemBottomSheet({
|
||||
super.key,
|
||||
required this.view,
|
||||
this.defaultType = MobileBottomSheetType.view,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
final MobileBottomSheetType defaultType;
|
||||
|
||||
@override
|
||||
State<MobileViewItemBottomSheet> createState() =>
|
||||
_MobileViewItemBottomSheetState();
|
||||
}
|
||||
|
||||
class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
|
||||
MobileBottomSheetType type = MobileBottomSheetType.view;
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
|
||||
type = widget.defaultType;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// header
|
||||
_buildHeader(),
|
||||
const VSpace(16),
|
||||
// body
|
||||
_buildBody(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
switch (type) {
|
||||
case MobileBottomSheetType.view:
|
||||
case MobileBottomSheetType.rename:
|
||||
// header
|
||||
return MobileViewItemBottomSheetHeader(
|
||||
showBackButton: type != MobileBottomSheetType.view,
|
||||
view: widget.view,
|
||||
onBack: () {
|
||||
setState(() {
|
||||
type = MobileBottomSheetType.view;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
switch (type) {
|
||||
case MobileBottomSheetType.view:
|
||||
return MobileViewItemBottomSheetBody(
|
||||
isFavorite: widget.view.isFavorite,
|
||||
onAction: (action) {
|
||||
switch (action) {
|
||||
case MobileViewItemBottomSheetBodyAction.rename:
|
||||
setState(() {
|
||||
type = MobileBottomSheetType.rename;
|
||||
});
|
||||
break;
|
||||
case MobileViewItemBottomSheetBodyAction.duplicate:
|
||||
context.pop();
|
||||
context.read<ViewBloc>().add(const ViewEvent.duplicate());
|
||||
break;
|
||||
case MobileViewItemBottomSheetBodyAction.share:
|
||||
// unimplemented
|
||||
context.pop();
|
||||
break;
|
||||
case MobileViewItemBottomSheetBodyAction.delete:
|
||||
context.pop();
|
||||
context.read<ViewBloc>().add(const ViewEvent.delete());
|
||||
|
||||
break;
|
||||
case MobileViewItemBottomSheetBodyAction.addToFavorites:
|
||||
case MobileViewItemBottomSheetBodyAction.removeFromFavorites:
|
||||
context.pop();
|
||||
context
|
||||
.read<FavoriteBloc>()
|
||||
.add(FavoriteEvent.toggle(widget.view));
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
case MobileBottomSheetType.rename:
|
||||
return MobileBottomSheetRenameWidget(
|
||||
name: widget.view.name,
|
||||
onRename: (name) {
|
||||
if (name != widget.view.name) {
|
||||
context.read<ViewBloc>().add(ViewEvent.rename(name));
|
||||
}
|
||||
context.pop();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@ -18,6 +19,7 @@ class MobileViewItemBottomSheetHeader extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// back button,
|
||||
showBackButton
|
||||
@ -31,23 +33,22 @@ class MobileViewItemBottomSheetHeader extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
: FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text: const Icon(
|
||||
Icons.close,
|
||||
),
|
||||
margin: EdgeInsets.zero,
|
||||
onTap: () {
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
// title
|
||||
Expanded(
|
||||
child: Text(
|
||||
view.name,
|
||||
style: theme.textTheme.labelSmall,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.close,
|
||||
color: theme.hintColor,
|
||||
),
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
Text(
|
||||
view.name,
|
||||
style: theme.textTheme.labelSmall,
|
||||
),
|
||||
const HSpace(24.0),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -117,33 +117,6 @@ class MobileViewBottomSheetBody extends StatelessWidget {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// undo, redo
|
||||
// Row(
|
||||
// mainAxisSize: MainAxisSize.max,
|
||||
// children: [
|
||||
// Expanded(
|
||||
// child: BottomSheetActionWidget(
|
||||
// svg: FlowySvgs.m_undo_m,
|
||||
// text: LocaleKeys.toolbar_undo.tr(),
|
||||
// onTap: () => onAction(
|
||||
// MobileViewBottomSheetBodyAction.undo,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// const HSpace(8),
|
||||
// Expanded(
|
||||
// child: BottomSheetActionWidget(
|
||||
// svg: FlowySvgs.m_redo_m,
|
||||
// text: LocaleKeys.toolbar_redo.tr(),
|
||||
// onTap: () => onAction(
|
||||
// MobileViewBottomSheetBodyAction.redo,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// const VSpace(8),
|
||||
|
||||
// rename, duplicate
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/page_item/mobile_slide_action_button.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
@ -51,7 +51,8 @@ enum MobilePaneActionType {
|
||||
final viewBloc = context.read<ViewBloc>();
|
||||
final favoriteBloc = context.read<FavoriteBloc>();
|
||||
showMobileBottomSheet(
|
||||
context: context,
|
||||
context,
|
||||
showDragHandle: true,
|
||||
builder: (context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
|
@ -1,22 +1,26 @@
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:appflowy/plugins/base/drag_handler.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
Future<void> showMobileBottomSheet({
|
||||
required BuildContext context,
|
||||
Future<T?> showMobileBottomSheet<T>(
|
||||
BuildContext context, {
|
||||
required WidgetBuilder builder,
|
||||
bool isDragEnabled = true,
|
||||
ShapeBorder? shape,
|
||||
bool isDragEnabled = true,
|
||||
bool resizeToAvoidBottomInset = true,
|
||||
EdgeInsets padding = const EdgeInsets.fromLTRB(16, 16, 16, 32),
|
||||
bool showDragHandle = false,
|
||||
bool showHeader = false,
|
||||
bool showCloseButton = false,
|
||||
String title = '', // only works if showHeader is true
|
||||
}) async {
|
||||
showModalBottomSheet(
|
||||
assert(() {
|
||||
if (showCloseButton || title.isNotEmpty) assert(showHeader);
|
||||
return true;
|
||||
}());
|
||||
|
||||
return showModalBottomSheet<T>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
enableDrag: isDragEnabled,
|
||||
@ -29,128 +33,74 @@ Future<void> showMobileBottomSheet({
|
||||
),
|
||||
),
|
||||
builder: (context) {
|
||||
final child = builder(context);
|
||||
if (resizeToAvoidBottomInset) {
|
||||
return AnimatedPadding(
|
||||
padding: padding +
|
||||
EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
duration: Duration.zero,
|
||||
child: child,
|
||||
);
|
||||
final List<Widget> children = [];
|
||||
|
||||
if (showDragHandle) {
|
||||
children.addAll([
|
||||
const VSpace(4),
|
||||
const DragHandler(),
|
||||
]);
|
||||
}
|
||||
return child;
|
||||
|
||||
if (showHeader) {
|
||||
children.addAll([
|
||||
const VSpace(4),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
showCloseButton
|
||||
? Padding(
|
||||
padding: EdgeInsets.only(left: padding.left),
|
||||
child: FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text: const Icon(
|
||||
Icons.close,
|
||||
size: 24,
|
||||
),
|
||||
margin: EdgeInsets.zero,
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
FlowyText(
|
||||
title,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
showCloseButton
|
||||
? HSpace(padding.right + 24)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
const VSpace(4),
|
||||
const Divider(),
|
||||
]);
|
||||
}
|
||||
|
||||
final child = builder(context);
|
||||
|
||||
if (resizeToAvoidBottomInset) {
|
||||
children.add(
|
||||
AnimatedPadding(
|
||||
padding: padding +
|
||||
EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
duration: Duration.zero,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
children.add(child);
|
||||
}
|
||||
|
||||
if (children.length == 1) {
|
||||
return children.first;
|
||||
}
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
enum MobileBottomSheetType {
|
||||
view,
|
||||
rename,
|
||||
}
|
||||
|
||||
class MobileViewItemBottomSheet extends StatefulWidget {
|
||||
const MobileViewItemBottomSheet({
|
||||
super.key,
|
||||
required this.view,
|
||||
this.defaultType = MobileBottomSheetType.view,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
final MobileBottomSheetType defaultType;
|
||||
|
||||
@override
|
||||
State<MobileViewItemBottomSheet> createState() =>
|
||||
_MobileViewItemBottomSheetState();
|
||||
}
|
||||
|
||||
class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
|
||||
MobileBottomSheetType type = MobileBottomSheetType.view;
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
|
||||
type = widget.defaultType;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// header
|
||||
_buildHeader(),
|
||||
const VSpace(16),
|
||||
// body
|
||||
_buildBody(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
switch (type) {
|
||||
case MobileBottomSheetType.view:
|
||||
case MobileBottomSheetType.rename:
|
||||
// header
|
||||
return MobileViewItemBottomSheetHeader(
|
||||
showBackButton: type != MobileBottomSheetType.view,
|
||||
view: widget.view,
|
||||
onBack: () {
|
||||
setState(() {
|
||||
type = MobileBottomSheetType.view;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
switch (type) {
|
||||
case MobileBottomSheetType.view:
|
||||
return MobileViewItemBottomSheetBody(
|
||||
isFavorite: widget.view.isFavorite,
|
||||
onAction: (action) {
|
||||
switch (action) {
|
||||
case MobileViewItemBottomSheetBodyAction.rename:
|
||||
setState(() {
|
||||
type = MobileBottomSheetType.rename;
|
||||
});
|
||||
break;
|
||||
case MobileViewItemBottomSheetBodyAction.duplicate:
|
||||
context.pop();
|
||||
context.read<ViewBloc>().add(const ViewEvent.duplicate());
|
||||
break;
|
||||
case MobileViewItemBottomSheetBodyAction.share:
|
||||
// unimplemented
|
||||
context.pop();
|
||||
break;
|
||||
case MobileViewItemBottomSheetBodyAction.delete:
|
||||
context.pop();
|
||||
context.read<ViewBloc>().add(const ViewEvent.delete());
|
||||
|
||||
break;
|
||||
case MobileViewItemBottomSheetBodyAction.addToFavorites:
|
||||
case MobileViewItemBottomSheetBodyAction.removeFromFavorites:
|
||||
context.pop();
|
||||
context
|
||||
.read<FavoriteBloc>()
|
||||
.add(FavoriteEvent.toggle(widget.view));
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
case MobileBottomSheetType.rename:
|
||||
return MobileBottomSheetRenameWidget(
|
||||
name: widget.view.name,
|
||||
onRename: (name) {
|
||||
if (name != widget.view.name) {
|
||||
context.read<ViewBloc>().add(ViewEvent.rename(name));
|
||||
}
|
||||
context.pop();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,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/bottom_sheet/bottom_sheet_action_widget.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/mobile_row_property_list.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
@ -12,8 +13,6 @@ import 'package:appflowy/plugins/database_view/grid/application/row/row_action_s
|
||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/mobile_row_property_list.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -181,12 +180,6 @@ class _MobileCardDetailScreenState extends State<MobileCardDetailScreen> {
|
||||
fieldController: widget.fieldController,
|
||||
),
|
||||
const Divider(),
|
||||
const VSpace(16),
|
||||
RowDocument(
|
||||
viewId: widget.rowController.viewId,
|
||||
rowId: widget.rowController.rowId,
|
||||
scrollController: widget.scrollController ?? ScrollController(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/personal_folder/mobile_home_personal_folder.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
||||
@ -48,17 +47,9 @@ class MobileFolders extends StatelessWidget {
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final menuState = context.watch<MenuBloc>().state;
|
||||
final favoriteState = context.watch<FavoriteBloc>().state;
|
||||
return SlidableAutoCloseBehavior(
|
||||
child: Column(
|
||||
children: [
|
||||
// TODO: Uncomment this when we have favorite folder in home page
|
||||
if (showFavorite && favoriteState.views.isNotEmpty) ...[
|
||||
MobileFavoriteFolder(
|
||||
views: favoriteState.views,
|
||||
),
|
||||
const VSpace(18.0),
|
||||
],
|
||||
MobilePersonalFolder(
|
||||
views: menuState.views,
|
||||
),
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/home.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/mobile_folders.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/mobile_home_page_header.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/recent_folder/mobile_home_recent_views.dart';
|
||||
@ -10,11 +11,10 @@ import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'home.dart';
|
||||
|
||||
class MobileHomeScreen extends StatelessWidget {
|
||||
const MobileHomeScreen({super.key});
|
||||
|
||||
@ -103,9 +103,9 @@ class MobileHomePage extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: MobileFolders(
|
||||
showFavorite: false,
|
||||
user: userProfile,
|
||||
workspaceSetting: workspaceSetting,
|
||||
showFavorite: false,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@ -129,26 +129,19 @@ class _TrashButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// TODO(yijing): improve style UI later
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: TextButton.icon(
|
||||
onPressed: () {
|
||||
context.push(MobileHomeTrashPage.routeName);
|
||||
},
|
||||
icon: FlowySvg(
|
||||
FlowySvgs.m_delete_m,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
label: Text(
|
||||
LocaleKeys.trash_text.tr(),
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
),
|
||||
style: const ButtonStyle(
|
||||
alignment: Alignment.centerLeft,
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
),
|
||||
return FlowyButton(
|
||||
expand: true,
|
||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||
leftIcon: FlowySvg(
|
||||
FlowySvgs.m_delete_m,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
leftIconSize: const Size.square(24),
|
||||
text: FlowyText(
|
||||
LocaleKeys.trash_text.tr(),
|
||||
fontSize: 18.0,
|
||||
),
|
||||
onTap: () => context.push(MobileHomeTrashPage.routeName),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
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_action_widget.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/trash/application/prelude.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
@ -32,8 +32,12 @@ class MobileHomeTrashPage extends StatelessWidget {
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
onPressed: () {
|
||||
final trashBloc = context.read<TrashBloc>();
|
||||
showFlowyMobileBottomSheet(
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
showCloseButton: true,
|
||||
showDragHandle: true,
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 32),
|
||||
title: LocaleKeys.trash_mobile_actions.tr(),
|
||||
builder: (_) => Row(
|
||||
children: [
|
||||
|
@ -3,6 +3,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/default_mobile_action_
|
||||
import 'package:appflowy/mobile/presentation/home/personal_folder/mobile_home_personal_folder_header.dart';
|
||||
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -56,13 +57,16 @@ class MobilePersonalFolder extends StatelessWidget {
|
||||
onSelected: (view) async {
|
||||
await context.pushView(view);
|
||||
},
|
||||
endActionPane: (context) => buildEndActionPane(context, [
|
||||
MobilePaneActionType.delete,
|
||||
view.isFavorite
|
||||
? MobilePaneActionType.removeFromFavorites
|
||||
: MobilePaneActionType.addToFavorites,
|
||||
MobilePaneActionType.more,
|
||||
]),
|
||||
endActionPane: (context) {
|
||||
final view = context.read<ViewBloc>().state.view;
|
||||
return buildEndActionPane(context, [
|
||||
MobilePaneActionType.delete,
|
||||
view.isFavorite
|
||||
? MobilePaneActionType.removeFromFavorites
|
||||
: MobilePaneActionType.addToFavorites,
|
||||
MobilePaneActionType.more,
|
||||
]);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -1,15 +1,12 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/recent_folder/mobile_recent_view.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy/workspace/application/recent/prelude.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:dartz/dartz.dart' hide State;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobileRecentFolder extends StatefulWidget {
|
||||
const MobileRecentFolder({super.key});
|
||||
@ -21,39 +18,36 @@ class MobileRecentFolder extends StatefulWidget {
|
||||
class _MobileRecentFolderState extends State<MobileRecentFolder> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: getIt<MobileRouterRecord>().lastPushedRouter,
|
||||
builder: (context, value, child) {
|
||||
return FutureBuilder<Either<RepeatedViewPB, FlowyError>>(
|
||||
future: FolderEventReadRecentViews().send(),
|
||||
builder: (context, snapshot) {
|
||||
final recentViews = snapshot.data
|
||||
?.fold<List<ViewPB>>(
|
||||
(l) => l.items,
|
||||
(r) => [],
|
||||
)
|
||||
// only keep the first 10 items.
|
||||
.reversed
|
||||
.take(10)
|
||||
.toList();
|
||||
return BlocProvider(
|
||||
create: (context) => RecentViewsBloc()
|
||||
..add(
|
||||
const RecentViewsEvent.initial(),
|
||||
),
|
||||
child: BlocBuilder<RecentViewsBloc, RecentViewsState>(
|
||||
builder: (context, state) {
|
||||
final recentViews = state
|
||||
.views
|
||||
// only keep the first 10 items.
|
||||
.reversed
|
||||
.take(10)
|
||||
.toList();
|
||||
|
||||
if (recentViews == null || recentViews.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
if (recentViews.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
_RecentViews(
|
||||
key: ValueKey(recentViews),
|
||||
// the recent views are in reverse order
|
||||
recentViews: recentViews,
|
||||
),
|
||||
const VSpace(12.0),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
return Column(
|
||||
children: [
|
||||
_RecentViews(
|
||||
key: ValueKey(recentViews),
|
||||
// the recent views are in reverse order
|
||||
recentViews: recentViews,
|
||||
),
|
||||
const VSpace(12.0),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_add_new_page.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item_add_button.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
@ -397,9 +396,12 @@ class _SingleMobileInnerViewItemState extends State<SingleMobileInnerViewItem> {
|
||||
return MobileViewAddButton(
|
||||
onPressed: () {
|
||||
final title = widget.view.name;
|
||||
showFlowyMobileBottomSheet(
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
title: title,
|
||||
showCloseButton: true,
|
||||
showDragHandle: true,
|
||||
builder: (_) {
|
||||
return AddNewPageWidgetBottomSheet(
|
||||
view: widget.view,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/util/theme_mode_extension.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -31,9 +31,13 @@ class ThemeSetting extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
showFlowyMobileBottomSheet(
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
showCloseButton: true,
|
||||
showDragHandle: true,
|
||||
title: LocaleKeys.settings_appearance_themeMode_label.tr(),
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 32),
|
||||
builder: (_) {
|
||||
return Column(
|
||||
children: [
|
||||
|
@ -45,7 +45,7 @@ class PersonalInfoSettingGroup extends StatelessWidget {
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
showMobileBottomSheet(
|
||||
context: context,
|
||||
context,
|
||||
builder: (_) {
|
||||
return EditUsernameBottomSheet(
|
||||
context,
|
||||
|
@ -15,7 +15,7 @@ class SheetPage {
|
||||
|
||||
void showPaginatedBottomSheet(BuildContext context, {required SheetPage page}) {
|
||||
showMobileBottomSheet(
|
||||
context: context,
|
||||
context,
|
||||
// Workaround for not causing drag to rebuild
|
||||
isDragEnabled: false,
|
||||
builder: (context) => FlowyBottomSheet(root: page),
|
||||
|
@ -106,7 +106,7 @@ class _DateCellState extends GridCellState<GridDateCell> {
|
||||
text: child,
|
||||
onTap: () {
|
||||
showMobileBottomSheet(
|
||||
context: context,
|
||||
context,
|
||||
padding: EdgeInsets.zero,
|
||||
builder: (context) {
|
||||
return MobileDateCellEditScreen(
|
||||
|
@ -207,7 +207,7 @@ class _SelectOptionWrapState extends State<SelectOptionWrap> {
|
||||
text: child,
|
||||
onTap: () {
|
||||
showMobileBottomSheet(
|
||||
context: context,
|
||||
context,
|
||||
padding: EdgeInsets.zero,
|
||||
builder: (context) {
|
||||
return MobileSelectOptionEditor(
|
||||
|
@ -1,7 +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/bottom_sheet/bottom_sheet_block_action_widget.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@ -67,8 +67,11 @@ class MobileBlockActionButtons extends StatelessWidget {
|
||||
}
|
||||
|
||||
void _showBottomSheet(BuildContext context) {
|
||||
showFlowyMobileBottomSheet(
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
showDragHandle: true,
|
||||
showCloseButton: true,
|
||||
title: LocaleKeys.document_plugins_action.tr(),
|
||||
builder: (context) {
|
||||
return BlockActionBottomSheet(
|
||||
|
@ -2,7 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
|
||||
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
|
||||
@ -427,11 +427,13 @@ class DocumentCoverState extends State<DocumentCover> {
|
||||
IntrinsicWidth(
|
||||
child: RoundedTextButton(
|
||||
onPressed: () {
|
||||
showFlowyMobileBottomSheet(
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
showDragHandle: true,
|
||||
showCloseButton: true,
|
||||
title:
|
||||
LocaleKeys.document_plugins_cover_changeCover.tr(),
|
||||
isScrollControlled: true,
|
||||
builder: (context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
|
@ -28,7 +28,7 @@ class _EmbedImageUrlWidgetState extends State<EmbedImageUrlWidget> {
|
||||
onChanged: (value) => inputText = value,
|
||||
onEditingComplete: () => widget.onSubmit(inputText),
|
||||
),
|
||||
const VSpace(5),
|
||||
const VSpace(8),
|
||||
SizedBox(
|
||||
width: 160,
|
||||
child: FlowyButton(
|
||||
|
@ -2,7 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
|
||||
@ -115,10 +115,12 @@ class ImagePlaceholderState extends State<ImagePlaceholder> {
|
||||
if (PlatformExtension.isDesktopOrWeb) {
|
||||
controller.show();
|
||||
} else {
|
||||
showFlowyMobileBottomSheet(
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
title: LocaleKeys.editor_image.tr(),
|
||||
isScrollControlled: true,
|
||||
showHeader: true,
|
||||
showCloseButton: true,
|
||||
showDragHandle: true,
|
||||
builder: (context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
|
@ -2,7 +2,6 @@ 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/bottom_sheet/bottom_sheet_block_action_widget.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/base/color/color_picker_screen.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -43,8 +42,12 @@ Future<void> _showBlockActionSheet(
|
||||
Node node,
|
||||
Selection selection,
|
||||
) async {
|
||||
final result = await showFlowyMobileBottomSheet<bool>(
|
||||
final result = await showMobileBottomSheet<bool>(
|
||||
context,
|
||||
showDragHandle: true,
|
||||
showCloseButton: true,
|
||||
showHeader: true,
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 32),
|
||||
title: LocaleKeys.document_plugins_action.tr(),
|
||||
builder: (context) {
|
||||
return BlockActionBottomSheet(
|
||||
|
@ -79,6 +79,13 @@ class _TextDecorationMenuState extends State<_TextDecorationMenu> {
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.editorState.selectionExtraInfo = null;
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final children = textDecorations
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_edit_link_widget.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@ -11,8 +11,11 @@ void showEditLinkBottomSheet(
|
||||
void Function(BuildContext context, String text, String href) onEdit,
|
||||
) {
|
||||
assert(text.isNotEmpty);
|
||||
showFlowyMobileBottomSheet(
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showCloseButton: true,
|
||||
showDragHandle: true,
|
||||
showHeader: true,
|
||||
title: LocaleKeys.editor_editLink.tr(),
|
||||
builder: (context) {
|
||||
return MobileBottomSheetEditLinkWidget(
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/network_monitor.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/plugins/document/application/prelude.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
|
||||
@ -164,7 +163,6 @@ void _resolveHomeDeps(GetIt getIt) {
|
||||
getIt.registerSingleton(FToast());
|
||||
|
||||
getIt.registerSingleton(MenuSharedState());
|
||||
getIt.registerSingleton(MobileRouterRecord());
|
||||
|
||||
getIt.registerFactoryParam<UserListener, UserProfilePB, void>(
|
||||
(user, _) => UserListener(userProfile: user),
|
||||
|
@ -0,0 +1,2 @@
|
||||
export 'recent_service.dart';
|
||||
export 'recent_views_bloc.dart';
|
@ -0,0 +1,61 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/core/notification/folder_notification.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/notification.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-notification/subject.pb.dart';
|
||||
import 'package:appflowy_backend/rust_stream.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
typedef RecentViewsUpdated = void Function(
|
||||
Either<FlowyError, RepeatedViewIdPB> result,
|
||||
);
|
||||
|
||||
class RecentViewsListener {
|
||||
StreamSubscription<SubscribeObject>? _streamSubscription;
|
||||
FolderNotificationParser? _parser;
|
||||
|
||||
RecentViewsUpdated? _recentViewsUpdated;
|
||||
|
||||
void start({
|
||||
RecentViewsUpdated? recentViewsUpdated,
|
||||
}) {
|
||||
_recentViewsUpdated = recentViewsUpdated;
|
||||
_parser = FolderNotificationParser(
|
||||
id: 'recent_views',
|
||||
callback: _observableCallback,
|
||||
);
|
||||
_streamSubscription = RustStreamReceiver.listen(
|
||||
(observable) => _parser?.parse(observable),
|
||||
);
|
||||
}
|
||||
|
||||
void _observableCallback(
|
||||
FolderNotification ty,
|
||||
Either<Uint8List, FlowyError> result,
|
||||
) {
|
||||
if (_recentViewsUpdated == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
result.fold(
|
||||
(payload) {
|
||||
final view = RepeatedViewIdPB.fromBuffer(payload);
|
||||
_recentViewsUpdated?.call(
|
||||
right(view),
|
||||
);
|
||||
},
|
||||
(error) => _recentViewsUpdated?.call(
|
||||
left(error),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
_parser = null;
|
||||
await _streamSubscription?.cancel();
|
||||
_recentViewsUpdated = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
class RecentService {
|
||||
Future<Either<Unit, FlowyError>> updateRecentViews(
|
||||
List<String> viewIds,
|
||||
bool addInRecent,
|
||||
) async {
|
||||
return FolderEventUpdateRecentViews(
|
||||
UpdateRecentViewPayloadPB(viewIds: viewIds, addInRecent: addInRecent),
|
||||
).send();
|
||||
}
|
||||
|
||||
Future<Either<RepeatedViewPB, FlowyError>> readRecentViews() {
|
||||
return FolderEventReadRecentViews().send();
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
import 'package:appflowy/workspace/application/recent/recent_listener.dart';
|
||||
import 'package:appflowy/workspace/application/recent/recent_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'recent_views_bloc.freezed.dart';
|
||||
|
||||
class RecentViewsBloc extends Bloc<RecentViewsEvent, RecentViewsState> {
|
||||
final _service = RecentService();
|
||||
final _listener = RecentViewsListener();
|
||||
|
||||
RecentViewsBloc() : super(RecentViewsState.initial()) {
|
||||
on<RecentViewsEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (e) async {
|
||||
_listener.start(
|
||||
recentViewsUpdated: (result) => _onRecentViewsUpdated(
|
||||
result,
|
||||
),
|
||||
);
|
||||
add(const RecentViewsEvent.fetchRecentViews());
|
||||
},
|
||||
addRecentViews: (e) async {
|
||||
await _service.updateRecentViews(e.viewIds, true);
|
||||
},
|
||||
removeRecentViews: (e) async {
|
||||
await _service.updateRecentViews(e.viewIds, false);
|
||||
},
|
||||
fetchRecentViews: (e) async {
|
||||
final result = await _service.readRecentViews();
|
||||
result.fold(
|
||||
(views) => emit(state.copyWith(views: views.items)),
|
||||
(error) => Log.error(error),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _listener.stop();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _onRecentViewsUpdated(
|
||||
Either<FlowyError, RepeatedViewIdPB> result,
|
||||
) {
|
||||
add(const RecentViewsEvent.fetchRecentViews());
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RecentViewsEvent with _$RecentViewsEvent {
|
||||
const factory RecentViewsEvent.initial() = Initial;
|
||||
const factory RecentViewsEvent.addRecentViews(List<String> viewIds) =
|
||||
AddRecentViews;
|
||||
const factory RecentViewsEvent.removeRecentViews(List<String> viewIds) =
|
||||
RemoveRecentViews;
|
||||
const factory RecentViewsEvent.fetchRecentViews() = FetchRecentViews;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RecentViewsState with _$RecentViewsState {
|
||||
const factory RecentViewsState({
|
||||
required List<ViewPB> views,
|
||||
}) = _RecentViewsState;
|
||||
|
||||
factory RecentViewsState.initial() => const RecentViewsState(
|
||||
views: [],
|
||||
);
|
||||
}
|
@ -3,11 +3,14 @@ import 'dart:convert';
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_listener.dart';
|
||||
import 'package:appflowy/workspace/application/recent/recent_service.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
@ -16,12 +19,14 @@ part 'view_bloc.freezed.dart';
|
||||
class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
||||
final ViewBackendService viewBackendSvc;
|
||||
final ViewListener listener;
|
||||
final FavoriteListener favoriteListener;
|
||||
final ViewPB view;
|
||||
|
||||
ViewBloc({
|
||||
required this.view,
|
||||
}) : viewBackendSvc = ViewBackendService(),
|
||||
listener = ViewListener(viewId: view.id),
|
||||
favoriteListener = FavoriteListener(),
|
||||
super(ViewState.init(view)) {
|
||||
on<ViewEvent>((event, emit) async {
|
||||
await event.map(
|
||||
@ -40,6 +45,17 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
||||
);
|
||||
},
|
||||
);
|
||||
favoriteListener.start(
|
||||
favoritesUpdated: (result, isFavorite) {
|
||||
result.fold((error) {}, (result) {
|
||||
final current =
|
||||
result.items.firstWhereOrNull((v) => v.id == view.id);
|
||||
if (current != null) {
|
||||
add(ViewEvent.viewDidUpdate(left(current)));
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
final isExpanded = await _getViewIsExpanded(view);
|
||||
await _loadViewsWhenExpanded(emit, isExpanded);
|
||||
},
|
||||
@ -88,6 +104,7 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
||||
(error) => state.copyWith(successOrFailure: right(error)),
|
||||
),
|
||||
);
|
||||
RecentService().updateRecentViews([view.id], false);
|
||||
},
|
||||
duplicate: (e) async {
|
||||
final result = await ViewBackendService.duplicate(view: view);
|
||||
@ -139,6 +156,7 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await listener.stop();
|
||||
await favoriteListener.stop();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ class FlowyButton extends StatelessWidget {
|
||||
final MainAxisAlignment mainAxisAlignment;
|
||||
final bool showDefaultBoxDecorationOnMobile;
|
||||
final double iconPadding;
|
||||
final bool expand;
|
||||
|
||||
const FlowyButton({
|
||||
Key? key,
|
||||
@ -50,6 +51,7 @@ class FlowyButton extends StatelessWidget {
|
||||
this.mainAxisAlignment = MainAxisAlignment.center,
|
||||
this.showDefaultBoxDecorationOnMobile = false,
|
||||
this.iconPadding = 6,
|
||||
this.expand = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -113,6 +115,7 @@ class FlowyButton extends StatelessWidget {
|
||||
Widget child = Row(
|
||||
mainAxisAlignment: mainAxisAlignment,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: expand ? MainAxisSize.max : MainAxisSize.min,
|
||||
children: children,
|
||||
);
|
||||
|
||||
|
@ -431,6 +431,17 @@ impl TryInto<MoveNestedViewParams> for MoveNestedViewPayloadPB {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct UpdateRecentViewPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_ids: Vec<String>,
|
||||
|
||||
// If true, the view will be added to the recent view list.
|
||||
// If false, the view will be removed from the recent view list.
|
||||
#[pb(index = 2)]
|
||||
pub add_in_recent: bool,
|
||||
}
|
||||
|
||||
// impl<'de> Deserialize<'de> for ViewDataType {
|
||||
// fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
|
||||
// where
|
||||
|
@ -158,6 +158,20 @@ pub(crate) async fn toggle_favorites_handler(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn update_recent_views_handler(
|
||||
data: AFPluginData<UpdateRecentViewPayloadPB>,
|
||||
folder: AFPluginState<Weak<FolderManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let params: UpdateRecentViewPayloadPB = data.into_inner();
|
||||
let folder = upgrade_folder(folder)?;
|
||||
if params.add_in_recent {
|
||||
let _ = folder.add_recent_views(params.view_ids).await;
|
||||
} else {
|
||||
let _ = folder.remove_recent_views(params.view_ids).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn set_latest_view_handler(
|
||||
data: AFPluginData<ViewIdPB>,
|
||||
folder: AFPluginState<Weak<FolderManager>>,
|
||||
|
@ -38,6 +38,7 @@ pub fn init(folder: Weak<FolderManager>) -> AFPlugin {
|
||||
.event(FolderEvent::ReadFavorites, read_favorites_handler)
|
||||
.event(FolderEvent::ReadRecentViews, read_recent_views_handler)
|
||||
.event(FolderEvent::ToggleFavorite, toggle_favorites_handler)
|
||||
.event(FolderEvent::UpdateRecentViews, update_recent_views_handler)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
|
||||
@ -149,4 +150,8 @@ pub enum FolderEvent {
|
||||
|
||||
#[event(output = "RepeatedViewPB")]
|
||||
ReadRecentViews = 36,
|
||||
|
||||
// used for add or remove recent views, like history
|
||||
#[event(input = "UpdateRecentViewPayloadPB")]
|
||||
UpdateRecentViews = 37,
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ use crate::entities::icon::UpdateViewIconParams;
|
||||
use crate::entities::{
|
||||
view_pb_with_child_views, view_pb_without_child_views, ChildViewUpdatePB, CreateViewParams,
|
||||
CreateWorkspaceParams, DeletedViewPB, FolderSnapshotPB, FolderSnapshotStatePB, FolderSyncStatePB,
|
||||
RepeatedTrashPB, RepeatedViewPB, UpdateViewParams, UserFolderPB, ViewPB, WorkspacePB,
|
||||
WorkspaceSettingPB,
|
||||
RepeatedTrashPB, RepeatedViewIdPB, RepeatedViewPB, UpdateViewParams, UserFolderPB, ViewPB,
|
||||
WorkspacePB, WorkspaceSettingPB,
|
||||
};
|
||||
use crate::notification::{
|
||||
send_notification, send_workspace_setting_notification, FolderNotification,
|
||||
@ -782,6 +782,32 @@ impl FolderManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add the view to the recent view list / history.
|
||||
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||
pub async fn add_recent_views(&self, view_ids: Vec<String>) -> FlowyResult<()> {
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
folder.add_recent_view_ids(view_ids);
|
||||
},
|
||||
);
|
||||
self.send_update_recent_views_notification().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add the view to the recent view list / history.
|
||||
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||
pub async fn remove_recent_views(&self, view_ids: Vec<String>) -> FlowyResult<()> {
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
folder.delete_recent_view_ids(view_ids);
|
||||
},
|
||||
);
|
||||
self.send_update_recent_views_notification().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Used by toggle_favorites to send notification to frontend, after the favorite status of view has been changed.It sends two distinct notifications: one to correctly update the concerned view's is_favorite status, and another to update the list of favorites that is to be displayed.
|
||||
async fn send_toggle_favorite_notification(&self, view_id: &str) {
|
||||
if let Ok(view) = self.get_view_pb(view_id).await {
|
||||
@ -802,6 +828,15 @@ impl FolderManager {
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_update_recent_views_notification(&self) {
|
||||
let recent_views = self.get_all_recent_sections().await;
|
||||
send_notification("recent_views", FolderNotification::DidUpdateRecentViews)
|
||||
.payload(RepeatedViewIdPB {
|
||||
items: recent_views.into_iter().map(|item| item.id).collect(),
|
||||
})
|
||||
.send();
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub(crate) async fn get_all_favorites(&self) -> Vec<SectionItem> {
|
||||
self.get_sections(Section::Favorite)
|
||||
|
@ -33,6 +33,8 @@ pub enum FolderNotification {
|
||||
|
||||
DidFavoriteView = 36,
|
||||
DidUnfavoriteView = 37,
|
||||
|
||||
DidUpdateRecentViews = 38,
|
||||
}
|
||||
|
||||
impl std::convert::From<FolderNotification> for i32 {
|
||||
|
Loading…
Reference in New Issue
Block a user