mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: mobile database settings rework (#4016)
* feat: mobile database settings rework * chore: clean
This commit is contained in:
parent
929508df16
commit
771dd9979f
@ -33,7 +33,7 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/or
|
|||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_editor.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_editor.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_menu.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_menu.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart';
|
import 'package:appflowy/plugins/database_view/widgets/setting/database_layout_selector.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/sort_button.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/sort_button.dart';
|
||||||
import 'package:appflowy/plugins/database_view/tab_bar/desktop/tab_bar_add_button.dart';
|
import 'package:appflowy/plugins/database_view/tab_bar/desktop/tab_bar_add_button.dart';
|
||||||
import 'package:appflowy/plugins/database_view/tab_bar/desktop/tab_bar_header.dart';
|
import 'package:appflowy/plugins/database_view/tab_bar/desktop/tab_bar_header.dart';
|
||||||
@ -51,6 +51,8 @@ import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart';
|
|||||||
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/row_property.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/row_property.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/setting/database_setting_action.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/setting/database_settings_list.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
|
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/setting/setting_property_list.dart';
|
import 'package:appflowy/plugins/database_view/widgets/setting/setting_property_list.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
@ -1138,7 +1140,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
|
|
||||||
/// Should call [tapDatabaseSettingButton] first.
|
/// Should call [tapDatabaseSettingButton] first.
|
||||||
Future<void> tapViewPropertiesButton() async {
|
Future<void> tapViewPropertiesButton() async {
|
||||||
final findSettingItem = find.byType(DatabaseSettingListPopover);
|
final findSettingItem = find.byType(DatabaseSettingsList);
|
||||||
final findLayoutButton = find.byWidgetPredicate(
|
final findLayoutButton = find.byWidgetPredicate(
|
||||||
(widget) =>
|
(widget) =>
|
||||||
widget is FlowyText &&
|
widget is FlowyText &&
|
||||||
@ -1155,7 +1157,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
|
|
||||||
/// Should call [tapDatabaseSettingButton] first.
|
/// Should call [tapDatabaseSettingButton] first.
|
||||||
Future<void> tapDatabaseLayoutButton() async {
|
Future<void> tapDatabaseLayoutButton() async {
|
||||||
final findSettingItem = find.byType(DatabaseSettingListPopover);
|
final findSettingItem = find.byType(DatabaseSettingsList);
|
||||||
final findLayoutButton = find.byWidgetPredicate(
|
final findLayoutButton = find.byWidgetPredicate(
|
||||||
(widget) =>
|
(widget) =>
|
||||||
widget is FlowyText &&
|
widget is FlowyText &&
|
||||||
@ -1171,7 +1173,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> tapCalendarLayoutSettingButton() async {
|
Future<void> tapCalendarLayoutSettingButton() async {
|
||||||
final findSettingItem = find.byType(DatabaseSettingListPopover);
|
final findSettingItem = find.byType(DatabaseSettingsList);
|
||||||
final findLayoutButton = find.byWidgetPredicate(
|
final findLayoutButton = find.byWidgetPredicate(
|
||||||
(widget) =>
|
(widget) =>
|
||||||
widget is FlowyText &&
|
widget is FlowyText &&
|
||||||
@ -1593,7 +1595,8 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
) async {
|
) async {
|
||||||
final field = find.byWidgetPredicate(
|
final field = find.byWidgetPredicate(
|
||||||
(widget) =>
|
(widget) =>
|
||||||
widget is DatabasePropertyCell && widget.fieldInfo.name == fieldName,
|
widget is DesktopDatabasePropertyCell &&
|
||||||
|
widget.fieldInfo.name == fieldName,
|
||||||
);
|
);
|
||||||
final toggleVisibilityButton =
|
final toggleVisibilityButton =
|
||||||
find.descendant(of: field, matching: find.byType(FlowyIconButton));
|
find.descendant(of: field, matching: find.byType(FlowyIconButton));
|
||||||
|
@ -158,9 +158,7 @@ class MobileDBFieldBottomSheetBody extends StatelessWidget {
|
|||||||
BottomSheetActionWidget(
|
BottomSheetActionWidget(
|
||||||
svg: FlowySvgs.date_s,
|
svg: FlowySvgs.date_s,
|
||||||
text: LocaleKeys.grid_field_editProperty.tr(),
|
text: LocaleKeys.grid_field_editProperty.tr(),
|
||||||
onTap: () {
|
onTap: () => onAction(MobileDBBottomSheetGeneralAction.typeOption),
|
||||||
onAction(MobileDBBottomSheetGeneralAction.typeOption);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const VSpace(8),
|
const VSpace(8),
|
||||||
Row(
|
Row(
|
||||||
@ -170,9 +168,8 @@ class MobileDBFieldBottomSheetBody extends StatelessWidget {
|
|||||||
child: BottomSheetActionWidget(
|
child: BottomSheetActionWidget(
|
||||||
svg: FlowySvgs.hide_m,
|
svg: FlowySvgs.hide_m,
|
||||||
text: LocaleKeys.grid_field_hide.tr(),
|
text: LocaleKeys.grid_field_hide.tr(),
|
||||||
onTap: () {
|
onTap: () =>
|
||||||
onAction(MobileDBBottomSheetGeneralAction.toggleVisibility);
|
onAction(MobileDBBottomSheetGeneralAction.toggleVisibility),
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const HSpace(8),
|
const HSpace(8),
|
||||||
|
@ -10,11 +10,12 @@ import 'package:go_router/go_router.dart';
|
|||||||
Future<void> showMobileBottomSheet({
|
Future<void> showMobileBottomSheet({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required WidgetBuilder builder,
|
required WidgetBuilder builder,
|
||||||
|
bool isDragEnabled = true,
|
||||||
}) async {
|
}) async {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
enableDrag: true,
|
enableDrag: isDragEnabled,
|
||||||
useSafeArea: true,
|
useSafeArea: true,
|
||||||
builder: builder,
|
builder: builder,
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/database/card/card_property_edit/mobile_field_editor.dart';
|
import 'package:appflowy/mobile/presentation/database/card/card_property_edit/mobile_field_editor.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -53,10 +52,6 @@ class _MobileCreateRowFieldScreenState
|
|||||||
),
|
),
|
||||||
body: MobileFieldEditor(
|
body: MobileFieldEditor(
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
typeOptionLoader: FieldTypeOptionLoader(
|
|
||||||
viewId: widget.viewId,
|
|
||||||
field: widget.typeOption.field_2,
|
|
||||||
),
|
|
||||||
fieldController: widget.fieldController,
|
fieldController: widget.fieldController,
|
||||||
field: widget.typeOption.field_2,
|
field: widget.typeOption.field_2,
|
||||||
),
|
),
|
||||||
|
@ -4,7 +4,6 @@ import 'package:appflowy/mobile/presentation/database/card/card_property_edit/mo
|
|||||||
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart';
|
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -57,10 +56,6 @@ class CardPropertyEditScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
body: MobileFieldEditor(
|
body: MobileFieldEditor(
|
||||||
viewId: cellContext.viewId,
|
viewId: cellContext.viewId,
|
||||||
typeOptionLoader: FieldTypeOptionLoader(
|
|
||||||
viewId: cellContext.viewId,
|
|
||||||
field: cellContext.fieldInfo.field,
|
|
||||||
),
|
|
||||||
fieldController: fieldController,
|
fieldController: fieldController,
|
||||||
field: cellContext.fieldInfo.field,
|
field: cellContext.fieldInfo.field,
|
||||||
),
|
),
|
||||||
|
@ -6,6 +6,9 @@ import 'package:appflowy/plugins/database_view/application/field/field_controlle
|
|||||||
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/setting/field_visibility_extension.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
@ -17,18 +20,21 @@ class MobileFieldEditor extends StatelessWidget {
|
|||||||
const MobileFieldEditor({
|
const MobileFieldEditor({
|
||||||
super.key,
|
super.key,
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.typeOptionLoader,
|
|
||||||
required this.field,
|
required this.field,
|
||||||
required this.fieldController,
|
required this.fieldController,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String viewId;
|
final String viewId;
|
||||||
final FieldController fieldController;
|
final FieldController fieldController;
|
||||||
final FieldTypeOptionLoader typeOptionLoader;
|
|
||||||
final FieldPB field;
|
final FieldPB field;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final typeOptionLoader = FieldTypeOptionLoader(
|
||||||
|
viewId: viewId,
|
||||||
|
field: field,
|
||||||
|
);
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) {
|
create: (context) {
|
||||||
return FieldEditorBloc(
|
return FieldEditorBloc(
|
||||||
@ -49,42 +55,35 @@ class MobileFieldEditor extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// field name
|
|
||||||
// TODO(yijing): improve hint text
|
// TODO(yijing): improve hint text
|
||||||
PropertyTitle(LocaleKeys.settings_user_name.tr()),
|
PropertyTitle(LocaleKeys.settings_user_name.tr()),
|
||||||
BlocSelector<FieldEditorBloc, FieldEditorState, String>(
|
BlocSelector<FieldEditorBloc, FieldEditorState, String>(
|
||||||
selector: (state) => state.field.name,
|
selector: (state) => state.field.name,
|
||||||
builder: (context, fieldName) {
|
builder: (context, fieldName) =>
|
||||||
return MobileFieldNameTextField(
|
MobileFieldNameTextField(text: fieldName),
|
||||||
text: fieldName,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child:
|
child: PropertyTitle(
|
||||||
PropertyTitle(LocaleKeys.grid_field_visibility.tr()),
|
LocaleKeys.grid_field_visibility.tr(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
VisibilitySwitch(
|
VisibilitySwitch(
|
||||||
isFieldHidden: state.field.visibility ==
|
isVisible:
|
||||||
FieldVisibility.AlwaysHidden,
|
state.field.visibility?.isVisibleState() ?? false,
|
||||||
onChanged: () {
|
onChanged: () => context.read<RowDetailBloc>().add(
|
||||||
context.read<RowDetailBloc>().add(
|
RowDetailEvent.toggleFieldVisibility(
|
||||||
RowDetailEvent.toggleFieldVisibility(
|
state.field.id,
|
||||||
state.field.id,
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const VSpace(8),
|
const VSpace(8),
|
||||||
// edit property type and settings
|
// edit property type and settings
|
||||||
if (!typeOptionLoader.field.isPrimary)
|
if (!typeOptionLoader.field.isPrimary)
|
||||||
MobileFieldTypeOptionEditor(
|
MobileFieldTypeOptionEditor(dataController: dataController),
|
||||||
dataController: dataController,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -97,11 +96,11 @@ class MobileFieldEditor extends StatelessWidget {
|
|||||||
class VisibilitySwitch extends StatefulWidget {
|
class VisibilitySwitch extends StatefulWidget {
|
||||||
const VisibilitySwitch({
|
const VisibilitySwitch({
|
||||||
super.key,
|
super.key,
|
||||||
required this.isFieldHidden,
|
required this.isVisible,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool isFieldHidden;
|
final bool isVisible;
|
||||||
final Function? onChanged;
|
final Function? onChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -109,18 +108,17 @@ class VisibilitySwitch extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _VisibilitySwitchState extends State<VisibilitySwitch> {
|
class _VisibilitySwitchState extends State<VisibilitySwitch> {
|
||||||
late bool _isFieldHidden = widget.isFieldHidden;
|
late bool _isVisible = widget.isVisible;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Switch.adaptive(
|
return Toggle(
|
||||||
activeColor: Theme.of(context).colorScheme.primary,
|
padding: EdgeInsets.zero,
|
||||||
value: !_isFieldHidden,
|
value: _isVisible,
|
||||||
onChanged: (bool value) {
|
style: ToggleStyle.mobile,
|
||||||
setState(() {
|
onChanged: (newValue) {
|
||||||
_isFieldHidden = !_isFieldHidden;
|
widget.onChanged?.call();
|
||||||
widget.onChanged?.call();
|
setState(() => _isVisible = newValue);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,23 +5,36 @@ class MobileSettingItem extends StatelessWidget {
|
|||||||
const MobileSettingItem({
|
const MobileSettingItem({
|
||||||
super.key,
|
super.key,
|
||||||
required this.name,
|
required this.name,
|
||||||
|
this.padding = const EdgeInsets.only(bottom: 4),
|
||||||
|
this.trailing,
|
||||||
|
this.leadingIcon,
|
||||||
this.subtitle,
|
this.subtitle,
|
||||||
required this.trailing,
|
|
||||||
this.onTap,
|
this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
|
final EdgeInsets padding;
|
||||||
|
final Widget? trailing;
|
||||||
|
final Widget? leadingIcon;
|
||||||
final Widget? subtitle;
|
final Widget? subtitle;
|
||||||
final Widget trailing;
|
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 4),
|
padding: padding,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: FlowyText.medium(
|
title: Row(
|
||||||
name,
|
children: [
|
||||||
fontSize: 14.0,
|
if (leadingIcon != null) ...[
|
||||||
|
leadingIcon!,
|
||||||
|
const HSpace(8),
|
||||||
|
],
|
||||||
|
FlowyText.medium(
|
||||||
|
name,
|
||||||
|
fontSize: 14.0,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
subtitle: subtitle,
|
subtitle: subtitle,
|
||||||
trailing: trailing,
|
trailing: trailing,
|
||||||
|
@ -0,0 +1,230 @@
|
|||||||
|
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||||
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
class SheetPage {
|
||||||
|
const SheetPage({
|
||||||
|
required this.title,
|
||||||
|
required this.body,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final Widget body;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showPaginatedBottomSheet(BuildContext context, {required SheetPage page}) {
|
||||||
|
showMobileBottomSheet(
|
||||||
|
context: context,
|
||||||
|
// Workaround for not causing drag to rebuild
|
||||||
|
isDragEnabled: false,
|
||||||
|
builder: (context) => FlowyBottomSheet(root: page),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef SheetNotifier = ValueNotifier<(SheetPage, bool)>;
|
||||||
|
|
||||||
|
class FlowyBottomSheet extends StatelessWidget {
|
||||||
|
FlowyBottomSheet({
|
||||||
|
super.key,
|
||||||
|
required this.root,
|
||||||
|
}) : _notifier = ValueNotifier((root, true));
|
||||||
|
|
||||||
|
final SheetPage root;
|
||||||
|
final SheetNotifier _notifier;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FlowyBottomSheetController(
|
||||||
|
key: UniqueKey(),
|
||||||
|
root: root,
|
||||||
|
onPageChanged: (page, isRoot) => _notifier.value = (page, isRoot),
|
||||||
|
child: _FlowyBottomSheetHandler(
|
||||||
|
root: root,
|
||||||
|
notifier: _notifier,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FlowyBottomSheetHandler extends StatefulWidget {
|
||||||
|
const _FlowyBottomSheetHandler({
|
||||||
|
required this.root,
|
||||||
|
required this.notifier,
|
||||||
|
});
|
||||||
|
|
||||||
|
final SheetPage root;
|
||||||
|
final ValueNotifier<(SheetPage, bool)> notifier;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_FlowyBottomSheetHandler> createState() =>
|
||||||
|
_FlowyBottomSheetHandlerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FlowyBottomSheetHandlerState extends State<_FlowyBottomSheetHandler> {
|
||||||
|
late SheetPage currentPage;
|
||||||
|
late bool isRoot;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
widget.notifier.addListener(_onPageChanged);
|
||||||
|
isRoot = true;
|
||||||
|
currentPage = widget.root;
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
currentPage = FlowyBottomSheetController.of(context)!.currentPage;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
widget.notifier.removeListener(_onPageChanged);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onPageChanged() {
|
||||||
|
final (page, root) = widget.notifier.value;
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
currentPage = page;
|
||||||
|
isRoot = root;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedSize(
|
||||||
|
duration: const Duration(milliseconds: 150),
|
||||||
|
child: FlowyBottomSheetPage(
|
||||||
|
isRoot: isRoot,
|
||||||
|
title: currentPage.title,
|
||||||
|
child: currentPage.body,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FlowyBottomSheetPage extends StatelessWidget {
|
||||||
|
const FlowyBottomSheetPage({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.child,
|
||||||
|
this.isRoot = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final Widget child;
|
||||||
|
final bool isRoot;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 32),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_SheetTopBar(title: title, isRoot: isRoot),
|
||||||
|
child,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SheetTopBar extends StatelessWidget {
|
||||||
|
const _SheetTopBar({
|
||||||
|
required this.title,
|
||||||
|
this.isRoot = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final bool isRoot;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
if (!isRoot) ...[
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => FlowyBottomSheetController.of(context)!.pop(),
|
||||||
|
icon: const Icon(Icons.arrow_back_ios),
|
||||||
|
),
|
||||||
|
const HSpace(6),
|
||||||
|
],
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: theme.textTheme.labelSmall,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: theme.hintColor,
|
||||||
|
),
|
||||||
|
onPressed: () => context.pop(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FlowyBottomSheetController extends InheritedWidget {
|
||||||
|
FlowyBottomSheetController({
|
||||||
|
super.key,
|
||||||
|
required SheetPage root,
|
||||||
|
this.onPageChanged,
|
||||||
|
required super.child,
|
||||||
|
FlowyBottomSheetControllerImpl? controller,
|
||||||
|
}) : _controller = controller ?? FlowyBottomSheetControllerImpl(root: root);
|
||||||
|
|
||||||
|
final Function(SheetPage page, bool isRoot)? onPageChanged;
|
||||||
|
|
||||||
|
final FlowyBottomSheetControllerImpl _controller;
|
||||||
|
SheetPage get currentPage => _controller.page;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool updateShouldNotify(covariant FlowyBottomSheetController oldWidget) {
|
||||||
|
return child != oldWidget.child ||
|
||||||
|
_controller.length != oldWidget._controller.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FlowyBottomSheetController? of(BuildContext context) {
|
||||||
|
return context
|
||||||
|
.dependOnInheritedWidgetOfExactType<FlowyBottomSheetController>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void push(SheetPage page) {
|
||||||
|
_controller.push(page);
|
||||||
|
onPageChanged?.call(_controller.page, _controller.isRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop() {
|
||||||
|
_controller.pop();
|
||||||
|
onPageChanged?.call(_controller.page, _controller.isRoot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FlowyBottomSheetControllerImpl {
|
||||||
|
FlowyBottomSheetControllerImpl({
|
||||||
|
required SheetPage root,
|
||||||
|
}) : _pages = [root];
|
||||||
|
|
||||||
|
final List<SheetPage> _pages;
|
||||||
|
SheetPage get page => _pages.last;
|
||||||
|
bool get isRoot => _pages.length == 1;
|
||||||
|
|
||||||
|
int get length => _pages.length;
|
||||||
|
|
||||||
|
void push(SheetPage page) {
|
||||||
|
_pages.add(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop() {
|
||||||
|
_pages.remove(page);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
@ -9,13 +11,13 @@ import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart
|
|||||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.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_bloc/flutter_bloc.dart';
|
||||||
import 'package:protobuf/protobuf.dart';
|
import 'package:protobuf/protobuf.dart';
|
||||||
|
|
||||||
abstract class ICalendarSetting {
|
abstract class ICalendarSetting {
|
||||||
|
const ICalendarSetting();
|
||||||
|
|
||||||
/// Returns the current layout settings for the calendar view.
|
/// Returns the current layout settings for the calendar view.
|
||||||
CalendarLayoutSettingPB? getLayoutSetting();
|
CalendarLayoutSettingPB? getLayoutSetting();
|
||||||
|
|
||||||
@ -42,25 +44,15 @@ class CalendarLayoutSetting extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
|
class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
|
||||||
late final PopoverMutex popoverMutex;
|
late final PopoverMutex popoverMutex = PopoverMutex();
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
popoverMutex = PopoverMutex();
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) {
|
create: (context) => CalendarSettingBloc(
|
||||||
return CalendarSettingBloc(
|
viewId: widget.viewId,
|
||||||
viewId: widget.viewId,
|
layoutSettings: widget.calendarSettingController.getLayoutSetting(),
|
||||||
layoutSettings: widget.calendarSettingController.getLayoutSetting(),
|
)..add(const CalendarSettingEvent.init()),
|
||||||
)..add(
|
|
||||||
const CalendarSettingEvent.init(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: BlocBuilder<CalendarSettingBloc, CalendarSettingState>(
|
child: BlocBuilder<CalendarSettingBloc, CalendarSettingState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final CalendarLayoutSettingPB? settings = state.layoutSetting
|
final CalendarLayoutSettingPB? settings = state.layoutSetting
|
||||||
@ -69,39 +61,34 @@ class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
|
|||||||
if (settings == null) {
|
if (settings == null) {
|
||||||
return const CircularProgressIndicator();
|
return const CircularProgressIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
final availableSettings = _availableCalendarSettings(settings);
|
final availableSettings = _availableCalendarSettings(settings);
|
||||||
final items = availableSettings.map((setting) {
|
final items = availableSettings.map((setting) {
|
||||||
switch (setting) {
|
switch (setting) {
|
||||||
case CalendarLayoutSettingAction.showWeekNumber:
|
case CalendarLayoutSettingAction.showWeekNumber:
|
||||||
return ShowWeekNumber(
|
return ShowWeekNumber(
|
||||||
showWeekNumbers: settings.showWeekNumbers,
|
showWeekNumbers: settings.showWeekNumbers,
|
||||||
onUpdated: (showWeekNumbers) {
|
onUpdated: (showWeekNumbers) => _updateLayoutSettings(
|
||||||
_updateLayoutSettings(
|
context,
|
||||||
context,
|
showWeekNumbers: showWeekNumbers,
|
||||||
showWeekNumbers: showWeekNumbers,
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
case CalendarLayoutSettingAction.showWeekends:
|
case CalendarLayoutSettingAction.showWeekends:
|
||||||
return ShowWeekends(
|
return ShowWeekends(
|
||||||
showWeekends: settings.showWeekends,
|
showWeekends: settings.showWeekends,
|
||||||
onUpdated: (showWeekends) {
|
onUpdated: (showWeekends) => _updateLayoutSettings(
|
||||||
_updateLayoutSettings(
|
context,
|
||||||
context,
|
showWeekends: showWeekends,
|
||||||
showWeekends: showWeekends,
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
case CalendarLayoutSettingAction.firstDayOfWeek:
|
case CalendarLayoutSettingAction.firstDayOfWeek:
|
||||||
return FirstDayOfWeek(
|
return FirstDayOfWeek(
|
||||||
firstDayOfWeek: settings.firstDayOfWeek,
|
firstDayOfWeek: settings.firstDayOfWeek,
|
||||||
popoverMutex: popoverMutex,
|
popoverMutex: popoverMutex,
|
||||||
onUpdated: (firstDayOfWeek) {
|
onUpdated: (firstDayOfWeek) => _updateLayoutSettings(
|
||||||
_updateLayoutSettings(
|
context,
|
||||||
context,
|
firstDayOfWeek: firstDayOfWeek,
|
||||||
firstDayOfWeek: firstDayOfWeek,
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
case CalendarLayoutSettingAction.layoutField:
|
case CalendarLayoutSettingAction.layoutField:
|
||||||
return LayoutDateField(
|
return LayoutDateField(
|
||||||
@ -109,15 +96,13 @@ class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
|
|||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
fieldId: settings.fieldId,
|
fieldId: settings.fieldId,
|
||||||
popoverMutex: popoverMutex,
|
popoverMutex: popoverMutex,
|
||||||
onUpdated: (fieldId) {
|
onUpdated: (fieldId) => _updateLayoutSettings(
|
||||||
_updateLayoutSettings(
|
context,
|
||||||
context,
|
layoutFieldId: fieldId,
|
||||||
layoutFieldId: fieldId,
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return const SizedBox();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
@ -127,10 +112,10 @@ class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
|
|||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
controller: ScrollController(),
|
controller: ScrollController(),
|
||||||
itemCount: items.length,
|
itemCount: items.length,
|
||||||
separatorBuilder: (context, index) =>
|
separatorBuilder: (_, __) =>
|
||||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
physics: StyledScrollPhysics(),
|
physics: StyledScrollPhysics(),
|
||||||
itemBuilder: (BuildContext context, int index) => items[index],
|
itemBuilder: (_, int index) => items[index],
|
||||||
padding: const EdgeInsets.all(6.0),
|
padding: const EdgeInsets.all(6.0),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -144,28 +129,14 @@ class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
|
|||||||
) {
|
) {
|
||||||
final List<CalendarLayoutSettingAction> settings = [
|
final List<CalendarLayoutSettingAction> settings = [
|
||||||
CalendarLayoutSettingAction.layoutField,
|
CalendarLayoutSettingAction.layoutField,
|
||||||
// CalendarLayoutSettingAction.layoutType,
|
|
||||||
// CalendarLayoutSettingAction.showWeekNumber,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
switch (layoutSettings.layoutTy) {
|
switch (layoutSettings.layoutTy) {
|
||||||
case CalendarLayoutPB.DayLayout:
|
case CalendarLayoutPB.DayLayout:
|
||||||
// settings.add(CalendarLayoutSettingAction.showTimeLine);
|
|
||||||
break;
|
break;
|
||||||
case CalendarLayoutPB.MonthLayout:
|
case CalendarLayoutPB.MonthLayout:
|
||||||
settings.addAll([
|
|
||||||
// CalendarLayoutSettingAction.showWeekends,
|
|
||||||
// if (layoutSettings.showWeekends)
|
|
||||||
CalendarLayoutSettingAction.firstDayOfWeek,
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
case CalendarLayoutPB.WeekLayout:
|
case CalendarLayoutPB.WeekLayout:
|
||||||
settings.addAll([
|
settings.add(CalendarLayoutSettingAction.firstDayOfWeek);
|
||||||
// CalendarLayoutSettingAction.showWeekends,
|
|
||||||
// if (layoutSettings.showWeekends)
|
|
||||||
CalendarLayoutSettingAction.firstDayOfWeek,
|
|
||||||
// CalendarLayoutSettingAction.showTimeLine,
|
|
||||||
]);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,16 +160,20 @@ class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
|
|||||||
if (showWeekends != null) {
|
if (showWeekends != null) {
|
||||||
setting.showWeekends = !showWeekends;
|
setting.showWeekends = !showWeekends;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showWeekNumbers != null) {
|
if (showWeekNumbers != null) {
|
||||||
setting.showWeekNumbers = !showWeekNumbers;
|
setting.showWeekNumbers = !showWeekNumbers;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstDayOfWeek != null) {
|
if (firstDayOfWeek != null) {
|
||||||
setting.firstDayOfWeek = firstDayOfWeek;
|
setting.firstDayOfWeek = firstDayOfWeek;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (layoutFieldId != null) {
|
if (layoutFieldId != null) {
|
||||||
setting.fieldId = layoutFieldId;
|
setting.fieldId = layoutFieldId;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
context
|
context
|
||||||
.read<CalendarSettingBloc>()
|
.read<CalendarSettingBloc>()
|
||||||
.add(CalendarSettingEvent.updateLayoutSetting(setting));
|
.add(CalendarSettingEvent.updateLayoutSetting(setting));
|
||||||
@ -208,21 +183,21 @@ class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LayoutDateField extends StatelessWidget {
|
class LayoutDateField extends StatelessWidget {
|
||||||
final String fieldId;
|
|
||||||
final String viewId;
|
|
||||||
final FieldController fieldController;
|
|
||||||
final PopoverMutex popoverMutex;
|
|
||||||
final Function(String fieldId) onUpdated;
|
|
||||||
|
|
||||||
const LayoutDateField({
|
const LayoutDateField({
|
||||||
|
super.key,
|
||||||
required this.fieldId,
|
required this.fieldId,
|
||||||
required this.fieldController,
|
required this.fieldController,
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.popoverMutex,
|
required this.popoverMutex,
|
||||||
required this.onUpdated,
|
required this.onUpdated,
|
||||||
super.key,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final String fieldId;
|
||||||
|
final String viewId;
|
||||||
|
final FieldController fieldController;
|
||||||
|
final PopoverMutex popoverMutex;
|
||||||
|
final Function(String fieldId) onUpdated;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppFlowyPopover(
|
return AppFlowyPopover(
|
||||||
@ -264,8 +239,8 @@ class LayoutDateField extends StatelessWidget {
|
|||||||
width: 200,
|
width: 200,
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemBuilder: (context, index) => items[index],
|
itemBuilder: (_, index) => items[index],
|
||||||
separatorBuilder: (context, index) =>
|
separatorBuilder: (_, __) =>
|
||||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
itemCount: items.length,
|
itemCount: items.length,
|
||||||
),
|
),
|
||||||
@ -288,21 +263,19 @@ class LayoutDateField extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ShowWeekNumber extends StatelessWidget {
|
class ShowWeekNumber extends StatelessWidget {
|
||||||
final bool showWeekNumbers;
|
|
||||||
final Function(bool showWeekNumbers) onUpdated;
|
|
||||||
|
|
||||||
const ShowWeekNumber({
|
const ShowWeekNumber({
|
||||||
|
super.key,
|
||||||
required this.showWeekNumbers,
|
required this.showWeekNumbers,
|
||||||
required this.onUpdated,
|
required this.onUpdated,
|
||||||
super.key,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final bool showWeekNumbers;
|
||||||
|
final Function(bool showWeekNumbers) onUpdated;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return _toggleItem(
|
return _toggleItem(
|
||||||
onToggle: (showWeekNumbers) {
|
onToggle: (showWeekNumbers) => onUpdated(!showWeekNumbers),
|
||||||
onUpdated(!showWeekNumbers);
|
|
||||||
},
|
|
||||||
value: showWeekNumbers,
|
value: showWeekNumbers,
|
||||||
text: LocaleKeys.calendar_settings_showWeekNumbers.tr(),
|
text: LocaleKeys.calendar_settings_showWeekNumbers.tr(),
|
||||||
);
|
);
|
||||||
@ -310,20 +283,19 @@ class ShowWeekNumber extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ShowWeekends extends StatelessWidget {
|
class ShowWeekends extends StatelessWidget {
|
||||||
final bool showWeekends;
|
|
||||||
final Function(bool showWeekends) onUpdated;
|
|
||||||
const ShowWeekends({
|
const ShowWeekends({
|
||||||
super.key,
|
super.key,
|
||||||
required this.showWeekends,
|
required this.showWeekends,
|
||||||
required this.onUpdated,
|
required this.onUpdated,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final bool showWeekends;
|
||||||
|
final Function(bool showWeekends) onUpdated;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return _toggleItem(
|
return _toggleItem(
|
||||||
onToggle: (showWeekends) {
|
onToggle: (showWeekends) => onUpdated(!showWeekends),
|
||||||
onUpdated(!showWeekends);
|
|
||||||
},
|
|
||||||
value: showWeekends,
|
value: showWeekends,
|
||||||
text: LocaleKeys.calendar_settings_showWeekends.tr(),
|
text: LocaleKeys.calendar_settings_showWeekends.tr(),
|
||||||
);
|
);
|
||||||
@ -331,16 +303,17 @@ class ShowWeekends extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FirstDayOfWeek extends StatelessWidget {
|
class FirstDayOfWeek extends StatelessWidget {
|
||||||
final int firstDayOfWeek;
|
|
||||||
final PopoverMutex popoverMutex;
|
|
||||||
final Function(int firstDayOfWeek) onUpdated;
|
|
||||||
const FirstDayOfWeek({
|
const FirstDayOfWeek({
|
||||||
super.key,
|
super.key,
|
||||||
required this.firstDayOfWeek,
|
required this.firstDayOfWeek,
|
||||||
required this.onUpdated,
|
|
||||||
required this.popoverMutex,
|
required this.popoverMutex,
|
||||||
|
required this.onUpdated,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final int firstDayOfWeek;
|
||||||
|
final PopoverMutex popoverMutex;
|
||||||
|
final Function(int firstDayOfWeek) onUpdated;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppFlowyPopover(
|
return AppFlowyPopover(
|
||||||
@ -370,8 +343,8 @@ class FirstDayOfWeek extends StatelessWidget {
|
|||||||
width: 100,
|
width: 100,
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemBuilder: (context, index) => items[index],
|
itemBuilder: (_, index) => items[index],
|
||||||
separatorBuilder: (context, index) =>
|
separatorBuilder: (_, __) =>
|
||||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
itemCount: len,
|
itemCount: len,
|
||||||
),
|
),
|
||||||
@ -425,18 +398,19 @@ enum CalendarLayoutSettingAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class StartFromButton extends StatelessWidget {
|
class StartFromButton extends StatelessWidget {
|
||||||
final int dayIndex;
|
|
||||||
final String title;
|
|
||||||
final bool isSelected;
|
|
||||||
final void Function(int) onTap;
|
|
||||||
const StartFromButton({
|
const StartFromButton({
|
||||||
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.dayIndex,
|
required this.dayIndex,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
super.key,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final int dayIndex;
|
||||||
|
final void Function(int) onTap;
|
||||||
|
final bool isSelected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
|
@ -28,7 +28,7 @@ import 'widgets/footer/grid_footer.dart';
|
|||||||
import 'widgets/header/grid_header.dart';
|
import 'widgets/header/grid_header.dart';
|
||||||
import 'widgets/row/row.dart';
|
import 'widgets/row/row.dart';
|
||||||
import 'widgets/shortcuts.dart';
|
import 'widgets/shortcuts.dart';
|
||||||
import 'widgets/toolbar/mobile_grid_setting.dart';
|
import '../../widgets/setting/mobile_database_settings_button.dart';
|
||||||
|
|
||||||
class MobileGridTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
|
class MobileGridTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
|
||||||
final _toggleExtension = ToggleExtensionNotifier();
|
final _toggleExtension = ToggleExtensionNotifier();
|
||||||
@ -49,7 +49,7 @@ class MobileGridTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget settingBar(BuildContext context, DatabaseController controller) {
|
Widget settingBar(BuildContext context, DatabaseController controller) {
|
||||||
return MobileGridSettingButton(
|
return MobileDatabaseSettingsButton(
|
||||||
key: _makeValueKey(controller),
|
key: _makeValueKey(controller),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
toggleExtension: _toggleExtension,
|
toggleExtension: _toggleExtension,
|
||||||
|
@ -1,105 +0,0 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/application/layout/layout_bloc.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart';
|
|
||||||
|
|
||||||
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:styled_widget/styled_widget.dart';
|
|
||||||
|
|
||||||
import '../../layout/sizes.dart';
|
|
||||||
|
|
||||||
class DatabaseLayoutList extends StatefulWidget {
|
|
||||||
final String viewId;
|
|
||||||
final DatabaseLayoutPB currentLayout;
|
|
||||||
const DatabaseLayoutList({
|
|
||||||
required this.viewId,
|
|
||||||
required this.currentLayout,
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => _DatabaseLayoutListState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DatabaseLayoutListState extends State<DatabaseLayoutList> {
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocProvider(
|
|
||||||
create: (context) => DatabaseLayoutBloc(
|
|
||||||
viewId: widget.viewId,
|
|
||||||
databaseLayout: widget.currentLayout,
|
|
||||||
)..add(const DatabaseLayoutEvent.initial()),
|
|
||||||
child: BlocBuilder<DatabaseLayoutBloc, DatabaseLayoutState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
final cells = DatabaseLayoutPB.values.map((layout) {
|
|
||||||
final isSelected = state.databaseLayout == layout;
|
|
||||||
return DatabaseViewLayoutCell(
|
|
||||||
databaseLayout: layout,
|
|
||||||
isSelected: isSelected,
|
|
||||||
onTap: (selectedLayout) {
|
|
||||||
context
|
|
||||||
.read<DatabaseLayoutBloc>()
|
|
||||||
.add(DatabaseLayoutEvent.updateLayout(selectedLayout));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
return ListView.separated(
|
|
||||||
controller: ScrollController(),
|
|
||||||
shrinkWrap: true,
|
|
||||||
itemCount: cells.length,
|
|
||||||
itemBuilder: (BuildContext context, int index) => cells[index],
|
|
||||||
separatorBuilder: (BuildContext context, int index) =>
|
|
||||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6.0),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DatabaseViewLayoutCell extends StatelessWidget {
|
|
||||||
final bool isSelected;
|
|
||||||
final DatabaseLayoutPB databaseLayout;
|
|
||||||
final void Function(DatabaseLayoutPB) onTap;
|
|
||||||
const DatabaseViewLayoutCell({
|
|
||||||
required this.databaseLayout,
|
|
||||||
required this.isSelected,
|
|
||||||
required this.onTap,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
Widget? checkmark;
|
|
||||||
if (isSelected) {
|
|
||||||
checkmark = const FlowySvg(FlowySvgs.check_s);
|
|
||||||
}
|
|
||||||
|
|
||||||
return SizedBox(
|
|
||||||
height: GridSize.popoverItemHeight,
|
|
||||||
child: FlowyButton(
|
|
||||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
|
||||||
text: FlowyText.medium(
|
|
||||||
databaseLayout.layoutName,
|
|
||||||
color: AFThemeExtension.of(context).textColor,
|
|
||||||
),
|
|
||||||
leftIcon: FlowySvg(
|
|
||||||
databaseLayout.icon,
|
|
||||||
color: Theme.of(context).iconTheme.color,
|
|
||||||
),
|
|
||||||
rightIcon: checkmark,
|
|
||||||
onTap: () => onTap(databaseLayout),
|
|
||||||
).padding(horizontal: 6.0),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:appflowy/plugins/database_view/application/tab_bar_bloc.dart';
|
import 'package:appflowy/plugins/database_view/application/tab_bar_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/mobile_grid_setting.dart';
|
import 'package:appflowy/plugins/database_view/widgets/setting/mobile_database_settings_button.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@ -34,7 +34,7 @@ class MobileTabBarHeader extends StatelessWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
MobileGridSettingButton(
|
MobileDatabaseSettingsButton(
|
||||||
controller: state
|
controller: state
|
||||||
.tabBarControllerByViewId[currentView.viewId]!
|
.tabBarControllerByViewId[currentView.viewId]!
|
||||||
.controller,
|
.controller,
|
||||||
|
@ -22,15 +22,16 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:protobuf/protobuf.dart' hide FieldInfo;
|
import 'package:protobuf/protobuf.dart' hide FieldInfo;
|
||||||
|
|
||||||
class DatabaseGroupList extends StatelessWidget {
|
class DatabaseGroupList extends StatelessWidget {
|
||||||
final String viewId;
|
|
||||||
final DatabaseController databaseController;
|
|
||||||
final VoidCallback onDismissed;
|
|
||||||
const DatabaseGroupList({
|
const DatabaseGroupList({
|
||||||
|
super.key,
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.databaseController,
|
required this.databaseController,
|
||||||
required this.onDismissed,
|
required this.onDismissed,
|
||||||
Key? key,
|
});
|
||||||
}) : super(key: key);
|
|
||||||
|
final String viewId;
|
||||||
|
final DatabaseController databaseController;
|
||||||
|
final VoidCallback onDismissed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -0,0 +1,158 @@
|
|||||||
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_item_widget.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/layout/layout_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
|
||||||
|
import 'package:appflowy/util/platform_extension.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart';
|
||||||
|
|
||||||
|
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 '../../grid/presentation/layout/sizes.dart';
|
||||||
|
|
||||||
|
class DatabaseLayoutSelector extends StatefulWidget {
|
||||||
|
const DatabaseLayoutSelector({
|
||||||
|
super.key,
|
||||||
|
required this.viewId,
|
||||||
|
required this.currentLayout,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String viewId;
|
||||||
|
final DatabaseLayoutPB currentLayout;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _DatabaseLayoutSelectorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DatabaseLayoutSelectorState extends State<DatabaseLayoutSelector> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => DatabaseLayoutBloc(
|
||||||
|
viewId: widget.viewId,
|
||||||
|
databaseLayout: widget.currentLayout,
|
||||||
|
)..add(const DatabaseLayoutEvent.initial()),
|
||||||
|
child: BlocBuilder<DatabaseLayoutBloc, DatabaseLayoutState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final cells = DatabaseLayoutPB.values
|
||||||
|
.map(
|
||||||
|
(layout) => DatabaseViewLayoutCell(
|
||||||
|
databaseLayout: layout,
|
||||||
|
isSelected: state.databaseLayout == layout,
|
||||||
|
onTap: (selectedLayout) => context
|
||||||
|
.read<DatabaseLayoutBloc>()
|
||||||
|
.add(DatabaseLayoutEvent.updateLayout(selectedLayout)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return ListView.separated(
|
||||||
|
controller: ScrollController(),
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: cells.length,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6.0),
|
||||||
|
itemBuilder: (_, int index) => cells[index],
|
||||||
|
separatorBuilder: (_, __) =>
|
||||||
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DatabaseViewLayoutCell extends StatelessWidget {
|
||||||
|
const DatabaseViewLayoutCell({
|
||||||
|
super.key,
|
||||||
|
required this.isSelected,
|
||||||
|
required this.databaseLayout,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool isSelected;
|
||||||
|
final DatabaseLayoutPB databaseLayout;
|
||||||
|
final void Function(DatabaseLayoutPB) onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (PlatformExtension.isMobile) {
|
||||||
|
return MobileDatabaseViewLayoutCell(
|
||||||
|
isSelected: isSelected,
|
||||||
|
databaseLayout: databaseLayout,
|
||||||
|
onTap: onTap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DesktopDatabaseViewLayoutCell(
|
||||||
|
isSelected: isSelected,
|
||||||
|
databaseLayout: databaseLayout,
|
||||||
|
onTap: onTap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DesktopDatabaseViewLayoutCell extends StatelessWidget {
|
||||||
|
const DesktopDatabaseViewLayoutCell({
|
||||||
|
super.key,
|
||||||
|
required this.isSelected,
|
||||||
|
required this.databaseLayout,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool isSelected;
|
||||||
|
final DatabaseLayoutPB databaseLayout;
|
||||||
|
final void Function(DatabaseLayoutPB) onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
|
child: SizedBox(
|
||||||
|
height: GridSize.popoverItemHeight,
|
||||||
|
child: FlowyButton(
|
||||||
|
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||||
|
text: FlowyText.medium(
|
||||||
|
databaseLayout.layoutName,
|
||||||
|
color: AFThemeExtension.of(context).textColor,
|
||||||
|
),
|
||||||
|
leftIcon: FlowySvg(
|
||||||
|
databaseLayout.icon,
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
rightIcon: isSelected ? const FlowySvg(FlowySvgs.check_s) : null,
|
||||||
|
onTap: () => onTap(databaseLayout),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MobileDatabaseViewLayoutCell extends StatelessWidget {
|
||||||
|
const MobileDatabaseViewLayoutCell({
|
||||||
|
super.key,
|
||||||
|
required this.isSelected,
|
||||||
|
required this.databaseLayout,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool isSelected;
|
||||||
|
final DatabaseLayoutPB databaseLayout;
|
||||||
|
final void Function(DatabaseLayoutPB) onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MobileSettingItem(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
name: databaseLayout.layoutName,
|
||||||
|
trailing: isSelected ? const FlowySvg(FlowySvgs.check_s) : null,
|
||||||
|
leadingIcon: FlowySvg(
|
||||||
|
databaseLayout.icon,
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
size: const Size.square(18),
|
||||||
|
),
|
||||||
|
onTap: () => onTap(databaseLayout),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,198 @@
|
|||||||
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_item_widget.dart';
|
||||||
|
import 'package:appflowy/mobile/presentation/widgets/flowy_paginated_bottom_sheet.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/setting/database_layout_selector.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/group/database_group.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/setting/mobile_calendar_settings.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/setting/setting_property_list.dart';
|
||||||
|
import 'package:appflowy/util/platform_extension.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.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:flutter/material.dart';
|
||||||
|
|
||||||
|
enum DatabaseSettingAction {
|
||||||
|
showProperties,
|
||||||
|
showLayout,
|
||||||
|
showGroup,
|
||||||
|
showCalendarLayout,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DatabaseSettingActionExtension on DatabaseSettingAction {
|
||||||
|
FlowySvgData iconData() {
|
||||||
|
switch (this) {
|
||||||
|
case DatabaseSettingAction.showProperties:
|
||||||
|
return FlowySvgs.properties_s;
|
||||||
|
case DatabaseSettingAction.showLayout:
|
||||||
|
return FlowySvgs.database_layout_m;
|
||||||
|
case DatabaseSettingAction.showGroup:
|
||||||
|
return FlowySvgs.group_s;
|
||||||
|
case DatabaseSettingAction.showCalendarLayout:
|
||||||
|
return FlowySvgs.calendar_layout_m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String title() {
|
||||||
|
switch (this) {
|
||||||
|
case DatabaseSettingAction.showProperties:
|
||||||
|
return LocaleKeys.grid_settings_properties.tr();
|
||||||
|
case DatabaseSettingAction.showLayout:
|
||||||
|
return LocaleKeys.grid_settings_databaseLayout.tr();
|
||||||
|
case DatabaseSettingAction.showGroup:
|
||||||
|
return LocaleKeys.grid_settings_group.tr();
|
||||||
|
case DatabaseSettingAction.showCalendarLayout:
|
||||||
|
return LocaleKeys.calendar_settings_name.tr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget build(
|
||||||
|
BuildContext context,
|
||||||
|
DatabaseController databaseController,
|
||||||
|
PopoverMutex popoverMutex,
|
||||||
|
) {
|
||||||
|
final popover = switch (this) {
|
||||||
|
DatabaseSettingAction.showLayout => DatabaseLayoutSelector(
|
||||||
|
viewId: databaseController.viewId,
|
||||||
|
currentLayout: databaseController.databaseLayout,
|
||||||
|
),
|
||||||
|
DatabaseSettingAction.showGroup => DatabaseGroupList(
|
||||||
|
viewId: databaseController.viewId,
|
||||||
|
databaseController: databaseController,
|
||||||
|
onDismissed: () {},
|
||||||
|
),
|
||||||
|
DatabaseSettingAction.showProperties => DatabasePropertyList(
|
||||||
|
viewId: databaseController.viewId,
|
||||||
|
fieldController: databaseController.fieldController,
|
||||||
|
),
|
||||||
|
DatabaseSettingAction.showCalendarLayout => CalendarLayoutSetting(
|
||||||
|
viewId: databaseController.viewId,
|
||||||
|
fieldController: databaseController.fieldController,
|
||||||
|
calendarSettingController: ICalendarSettingImpl(
|
||||||
|
databaseController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return AppFlowyPopover(
|
||||||
|
triggerActions: PlatformExtension.isMobile
|
||||||
|
? PopoverTriggerFlags.none
|
||||||
|
: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
||||||
|
direction: PopoverDirection.leftWithTopAligned,
|
||||||
|
mutex: popoverMutex,
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
offset: const Offset(-14, 0),
|
||||||
|
child: PlatformExtension.isMobile
|
||||||
|
? MobileSettingItem(
|
||||||
|
name: title(),
|
||||||
|
trailing: _trailingFromSetting(
|
||||||
|
context,
|
||||||
|
databaseController.databaseLayout,
|
||||||
|
),
|
||||||
|
leadingIcon: FlowySvg(
|
||||||
|
iconData(),
|
||||||
|
size: const Size.square(18),
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
onTap: _actionFromSetting(context, databaseController),
|
||||||
|
)
|
||||||
|
: SizedBox(
|
||||||
|
height: GridSize.popoverItemHeight,
|
||||||
|
child: FlowyButton(
|
||||||
|
onTap: null,
|
||||||
|
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||||
|
text: FlowyText.medium(
|
||||||
|
title(),
|
||||||
|
color: AFThemeExtension.of(context).textColor,
|
||||||
|
),
|
||||||
|
leftIcon: FlowySvg(
|
||||||
|
iconData(),
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
popupBuilder: (context) => popover,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
VoidCallback? _actionFromSetting(
|
||||||
|
BuildContext context,
|
||||||
|
DatabaseController databaseController,
|
||||||
|
) =>
|
||||||
|
switch (this) {
|
||||||
|
DatabaseSettingAction.showLayout => () =>
|
||||||
|
_showLayoutSettings(context, databaseController),
|
||||||
|
DatabaseSettingAction.showProperties => () =>
|
||||||
|
_showPropertiesSettings(context, databaseController),
|
||||||
|
DatabaseSettingAction.showCalendarLayout => () =>
|
||||||
|
_showCalendarSettings(context, databaseController),
|
||||||
|
// Group Settings
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
void _showLayoutSettings(
|
||||||
|
BuildContext context,
|
||||||
|
DatabaseController databaseController,
|
||||||
|
) =>
|
||||||
|
FlowyBottomSheetController.of(context)!.push(
|
||||||
|
SheetPage(
|
||||||
|
title: LocaleKeys.settings_mobile_selectLayout.tr(),
|
||||||
|
body: DatabaseLayoutSelector(
|
||||||
|
viewId: databaseController.viewId,
|
||||||
|
currentLayout: databaseController.databaseLayout,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
void _showPropertiesSettings(
|
||||||
|
BuildContext context,
|
||||||
|
DatabaseController databaseController,
|
||||||
|
) =>
|
||||||
|
FlowyBottomSheetController.of(context)!.push(
|
||||||
|
SheetPage(
|
||||||
|
title: LocaleKeys.grid_settings_properties.tr(),
|
||||||
|
body: DatabasePropertyList(
|
||||||
|
viewId: databaseController.viewId,
|
||||||
|
fieldController: databaseController.fieldController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
void _showCalendarSettings(
|
||||||
|
BuildContext context,
|
||||||
|
DatabaseController databaseController,
|
||||||
|
) =>
|
||||||
|
FlowyBottomSheetController.of(context)!.push(
|
||||||
|
SheetPage(
|
||||||
|
title: LocaleKeys.calendar_settings_name.tr(),
|
||||||
|
body: MobileCalendarLayoutSetting(
|
||||||
|
viewId: databaseController.viewId,
|
||||||
|
fieldController: databaseController.fieldController,
|
||||||
|
calendarSettingController: ICalendarSettingImpl(
|
||||||
|
databaseController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget? _trailingFromSetting(BuildContext context, DatabaseLayoutPB layout) =>
|
||||||
|
switch (this) {
|
||||||
|
DatabaseSettingAction.showLayout => Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
FlowyText(
|
||||||
|
layout.name,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
const Icon(Icons.chevron_right),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/setting/database_setting_action.dart';
|
||||||
|
import 'package:appflowy/util/platform_extension.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||||
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
|
||||||
|
class DatabaseSettingsList extends StatefulWidget {
|
||||||
|
const DatabaseSettingsList({
|
||||||
|
super.key,
|
||||||
|
required this.databaseController,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DatabaseController databaseController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _DatabaseSettingsListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DatabaseSettingsListState extends State<DatabaseSettingsList> {
|
||||||
|
late final PopoverMutex popoverMutex = PopoverMutex();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cells =
|
||||||
|
actionsForDatabaseLayout(widget.databaseController.databaseLayout)
|
||||||
|
.map(
|
||||||
|
(action) => action.build(
|
||||||
|
context,
|
||||||
|
widget.databaseController,
|
||||||
|
popoverMutex,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
controller: ScrollController(),
|
||||||
|
itemCount: cells.length,
|
||||||
|
separatorBuilder: (context, index) =>
|
||||||
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
|
physics: StyledScrollPhysics(),
|
||||||
|
itemBuilder: (BuildContext context, int index) => cells[index],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the list of actions that should be shown for the given database layout.
|
||||||
|
List<DatabaseSettingAction> actionsForDatabaseLayout(DatabaseLayoutPB? layout) {
|
||||||
|
switch (layout) {
|
||||||
|
case DatabaseLayoutPB.Board:
|
||||||
|
return [
|
||||||
|
DatabaseSettingAction.showProperties,
|
||||||
|
DatabaseSettingAction.showLayout,
|
||||||
|
if (!PlatformExtension.isMobile) DatabaseSettingAction.showGroup,
|
||||||
|
];
|
||||||
|
case DatabaseLayoutPB.Calendar:
|
||||||
|
return [
|
||||||
|
DatabaseSettingAction.showProperties,
|
||||||
|
DatabaseSettingAction.showLayout,
|
||||||
|
DatabaseSettingAction.showCalendarLayout,
|
||||||
|
];
|
||||||
|
case DatabaseLayoutPB.Grid:
|
||||||
|
return [
|
||||||
|
DatabaseSettingAction.showProperties,
|
||||||
|
DatabaseSettingAction.showLayout,
|
||||||
|
];
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
|
||||||
|
|
||||||
|
extension ToggleVisibility on FieldVisibility {
|
||||||
|
FieldVisibility toggle() => switch (this) {
|
||||||
|
FieldVisibility.AlwaysShown => FieldVisibility.AlwaysHidden,
|
||||||
|
FieldVisibility.AlwaysHidden => FieldVisibility.AlwaysShown,
|
||||||
|
_ => FieldVisibility.AlwaysHidden,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool isVisibleState() => switch (this) {
|
||||||
|
FieldVisibility.AlwaysShown => true,
|
||||||
|
FieldVisibility.HideWhenEmpty => true,
|
||||||
|
FieldVisibility.AlwaysHidden => false,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,349 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||||
|
import 'package:appflowy/mobile/presentation/widgets/flowy_paginated_bottom_sheet.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/calendar/application/calendar_setting_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/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_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:protobuf/protobuf.dart' hide FieldInfo;
|
||||||
|
|
||||||
|
class MobileCalendarLayoutSetting extends StatefulWidget {
|
||||||
|
const MobileCalendarLayoutSetting({
|
||||||
|
super.key,
|
||||||
|
required this.viewId,
|
||||||
|
required this.fieldController,
|
||||||
|
required this.calendarSettingController,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String viewId;
|
||||||
|
final FieldController fieldController;
|
||||||
|
final ICalendarSetting calendarSettingController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MobileCalendarLayoutSetting> createState() =>
|
||||||
|
_MobileCalendarLayoutSettingState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MobileCalendarLayoutSettingState
|
||||||
|
extends State<MobileCalendarLayoutSetting> {
|
||||||
|
late final PopoverMutex popoverMutex = PopoverMutex();
|
||||||
|
late final CalendarSettingBloc calendarSettingBloc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
calendarSettingBloc = CalendarSettingBloc(
|
||||||
|
viewId: widget.viewId,
|
||||||
|
layoutSettings: widget.calendarSettingController.getLayoutSetting(),
|
||||||
|
)..add(const CalendarSettingEvent.init());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider<CalendarSettingBloc>.value(
|
||||||
|
value: calendarSettingBloc,
|
||||||
|
child: BlocBuilder<CalendarSettingBloc, CalendarSettingState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final CalendarLayoutSettingPB? settings = state.layoutSetting
|
||||||
|
.foldLeft(null, (previous, settings) => settings);
|
||||||
|
|
||||||
|
if (settings == null) {
|
||||||
|
return const CircularProgressIndicator();
|
||||||
|
}
|
||||||
|
|
||||||
|
final availableSettings = _availableCalendarSettings(settings);
|
||||||
|
final items = availableSettings.map((setting) {
|
||||||
|
switch (setting) {
|
||||||
|
case CalendarLayoutSettingAction.firstDayOfWeek:
|
||||||
|
return MobileFirstDayOfWeekSetting(
|
||||||
|
selectedDay: settings.firstDayOfWeek,
|
||||||
|
onUpdated: (firstDayOfWeek) => _updateLayoutSettings(
|
||||||
|
context,
|
||||||
|
firstDayOfWeek: firstDayOfWeek,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case CalendarLayoutSettingAction.layoutField:
|
||||||
|
return MobileLayoutDateField(
|
||||||
|
fieldController: widget.fieldController,
|
||||||
|
viewId: widget.viewId,
|
||||||
|
fieldId: settings.fieldId,
|
||||||
|
popoverMutex: popoverMutex,
|
||||||
|
onUpdated: (fieldId) => _updateLayoutSettings(
|
||||||
|
context,
|
||||||
|
layoutFieldId: fieldId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
controller: ScrollController(),
|
||||||
|
itemCount: items.length,
|
||||||
|
separatorBuilder: (context, index) =>
|
||||||
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
|
physics: StyledScrollPhysics(),
|
||||||
|
itemBuilder: (_, int index) => items[index],
|
||||||
|
padding: const EdgeInsets.all(6.0),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CalendarLayoutSettingAction> _availableCalendarSettings(
|
||||||
|
CalendarLayoutSettingPB layoutSettings,
|
||||||
|
) {
|
||||||
|
final List<CalendarLayoutSettingAction> settings = [
|
||||||
|
CalendarLayoutSettingAction.layoutField,
|
||||||
|
];
|
||||||
|
|
||||||
|
switch (layoutSettings.layoutTy) {
|
||||||
|
case CalendarLayoutPB.DayLayout:
|
||||||
|
break;
|
||||||
|
case CalendarLayoutPB.MonthLayout:
|
||||||
|
case CalendarLayoutPB.WeekLayout:
|
||||||
|
settings.add(CalendarLayoutSettingAction.firstDayOfWeek);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateLayoutSettings(
|
||||||
|
BuildContext context, {
|
||||||
|
bool? showWeekends,
|
||||||
|
bool? showWeekNumbers,
|
||||||
|
int? firstDayOfWeek,
|
||||||
|
String? layoutFieldId,
|
||||||
|
}) {
|
||||||
|
CalendarLayoutSettingPB setting = calendarSettingBloc.state.layoutSetting
|
||||||
|
.foldLeft(null, (previous, settings) => settings)!;
|
||||||
|
setting.freeze();
|
||||||
|
setting = setting.rebuild((setting) {
|
||||||
|
if (showWeekends != null) {
|
||||||
|
setting.showWeekends = !showWeekends;
|
||||||
|
}
|
||||||
|
if (showWeekNumbers != null) {
|
||||||
|
setting.showWeekNumbers = !showWeekNumbers;
|
||||||
|
}
|
||||||
|
if (firstDayOfWeek != null) {
|
||||||
|
setting.firstDayOfWeek = firstDayOfWeek;
|
||||||
|
}
|
||||||
|
if (layoutFieldId != null) {
|
||||||
|
setting.fieldId = layoutFieldId;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
calendarSettingBloc.add(CalendarSettingEvent.updateLayoutSetting(setting));
|
||||||
|
widget.calendarSettingController.updateLayoutSettings(setting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MobileLayoutDateField extends StatelessWidget {
|
||||||
|
const MobileLayoutDateField({
|
||||||
|
super.key,
|
||||||
|
required this.fieldId,
|
||||||
|
required this.fieldController,
|
||||||
|
required this.viewId,
|
||||||
|
required this.popoverMutex,
|
||||||
|
required this.onUpdated,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String fieldId;
|
||||||
|
final String viewId;
|
||||||
|
final FieldController fieldController;
|
||||||
|
final PopoverMutex popoverMutex;
|
||||||
|
final Function(String fieldId) onUpdated;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => DatabasePropertyBloc(
|
||||||
|
viewId: viewId,
|
||||||
|
fieldController: fieldController,
|
||||||
|
)..add(const DatabasePropertyEvent.initial()),
|
||||||
|
child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final items = state.fieldContexts
|
||||||
|
.where((field) => field.fieldType == FieldType.DateTime)
|
||||||
|
.toList();
|
||||||
|
final selected = items.firstWhere((field) => field.id == fieldId);
|
||||||
|
|
||||||
|
return MobileSettingItem(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
name: LocaleKeys.calendar_settings_layoutDateField.tr(),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: FlowyText(
|
||||||
|
selected.name,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(Icons.chevron_right),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () => _showSelector(context, items),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showSelector(BuildContext context, List<FieldInfo> items) =>
|
||||||
|
FlowyBottomSheetController.of(context)!.push(
|
||||||
|
SheetPage(
|
||||||
|
title: LocaleKeys.settings_mobile_selectStartingDay.tr(),
|
||||||
|
body: MobileCalendarLayoutSelector(
|
||||||
|
fieldId: fieldId,
|
||||||
|
items: items,
|
||||||
|
onUpdated: onUpdated,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MobileCalendarLayoutSelector extends StatefulWidget {
|
||||||
|
const MobileCalendarLayoutSelector({
|
||||||
|
super.key,
|
||||||
|
required this.fieldId,
|
||||||
|
required this.items,
|
||||||
|
required this.onUpdated,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String fieldId;
|
||||||
|
final List<FieldInfo> items;
|
||||||
|
final Function(String fieldId) onUpdated;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MobileCalendarLayoutSelector> createState() =>
|
||||||
|
_MobileCalendarLayoutSelectorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MobileCalendarLayoutSelectorState
|
||||||
|
extends State<MobileCalendarLayoutSelector> {
|
||||||
|
late String _selectedField = widget.fieldId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: widget.items.length,
|
||||||
|
separatorBuilder: (_, __) => const VSpace(4),
|
||||||
|
itemBuilder: (_, index) => MobileSettingItem(
|
||||||
|
name: widget.items[index].name,
|
||||||
|
trailing: _selectedField == widget.items[index].id
|
||||||
|
? const FlowySvg(FlowySvgs.check_s)
|
||||||
|
: null,
|
||||||
|
onTap: () {
|
||||||
|
final selected = widget.items[index].id;
|
||||||
|
widget.onUpdated(selected);
|
||||||
|
setState(() => _selectedField = selected);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const weekdayOptions = 2;
|
||||||
|
|
||||||
|
class MobileFirstDayOfWeekSetting extends StatelessWidget {
|
||||||
|
const MobileFirstDayOfWeekSetting({
|
||||||
|
super.key,
|
||||||
|
required this.selectedDay,
|
||||||
|
required this.onUpdated,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int selectedDay;
|
||||||
|
final Function(int firstDayOfWeek) onUpdated;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final symbols = DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
|
||||||
|
final weekdays = symbols.WEEKDAYS.take(weekdayOptions).toList();
|
||||||
|
|
||||||
|
return MobileSettingItem(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
name: LocaleKeys.calendar_settings_firstDayOfWeek.tr(),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: FlowyText(
|
||||||
|
weekdays[selectedDay],
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(Icons.chevron_right),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () => _showSelector(context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showSelector(BuildContext context) =>
|
||||||
|
FlowyBottomSheetController.of(context)!.push(
|
||||||
|
SheetPage(
|
||||||
|
title: LocaleKeys.calendar_settings_layoutDateField.tr(),
|
||||||
|
body: MobileFirstDayOfWeekSelector(
|
||||||
|
selectedDay: selectedDay,
|
||||||
|
onUpdated: onUpdated,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MobileFirstDayOfWeekSelector extends StatefulWidget {
|
||||||
|
const MobileFirstDayOfWeekSelector({
|
||||||
|
super.key,
|
||||||
|
required this.selectedDay,
|
||||||
|
required this.onUpdated,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int selectedDay;
|
||||||
|
final Function(int firstDayOfWeek) onUpdated;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MobileFirstDayOfWeekSelector> createState() =>
|
||||||
|
_MobileFirstDayOfWeekSelectorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MobileFirstDayOfWeekSelectorState
|
||||||
|
extends State<MobileFirstDayOfWeekSelector> {
|
||||||
|
late int _selectedDay = widget.selectedDay;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final symbols = DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
|
||||||
|
final weekdays = symbols.WEEKDAYS.take(weekdayOptions).toList();
|
||||||
|
|
||||||
|
return ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: weekdayOptions,
|
||||||
|
separatorBuilder: (_, __) => const VSpace(4),
|
||||||
|
itemBuilder: (_, index) => MobileSettingItem(
|
||||||
|
name: weekdays[index],
|
||||||
|
trailing:
|
||||||
|
_selectedDay == index ? const FlowySvg(FlowySvgs.check_s) : null,
|
||||||
|
onTap: () {
|
||||||
|
widget.onUpdated(index);
|
||||||
|
setState(() => _selectedDay = index);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/mobile_field_name_text_field.dart';
|
||||||
|
import 'package:appflowy/mobile/presentation/database/card/card_property_edit/mobile_field_type_option_editor.dart';
|
||||||
|
import 'package:appflowy/mobile/presentation/database/card/card_property_edit/widgets/property_title.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/setting/field_visibility_extension.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class MobileDatabasePropertyEditor extends StatefulWidget {
|
||||||
|
const MobileDatabasePropertyEditor({
|
||||||
|
super.key,
|
||||||
|
required this.viewId,
|
||||||
|
required this.fieldInfo,
|
||||||
|
required this.fieldController,
|
||||||
|
required this.bloc,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String viewId;
|
||||||
|
final FieldInfo fieldInfo;
|
||||||
|
final FieldController fieldController;
|
||||||
|
final DatabasePropertyBloc bloc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MobileDatabasePropertyEditor> createState() =>
|
||||||
|
_MobileDatabasePropertyEditorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MobileDatabasePropertyEditorState
|
||||||
|
extends State<MobileDatabasePropertyEditor> {
|
||||||
|
late FieldVisibility _visibility =
|
||||||
|
widget.fieldInfo.visibility ?? FieldVisibility.AlwaysShown;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final typeOptionLoader = FieldTypeOptionLoader(
|
||||||
|
viewId: widget.viewId,
|
||||||
|
field: widget.fieldInfo.field,
|
||||||
|
);
|
||||||
|
|
||||||
|
return MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider<DatabasePropertyBloc>.value(value: widget.bloc),
|
||||||
|
BlocProvider<FieldEditorBloc>(
|
||||||
|
create: (context) => FieldEditorBloc(
|
||||||
|
viewId: widget.viewId,
|
||||||
|
loader: typeOptionLoader,
|
||||||
|
field: widget.fieldInfo.field,
|
||||||
|
fieldController: widget.fieldController,
|
||||||
|
)..add(const FieldEditorEvent.initial()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>(
|
||||||
|
builder: (context, _) {
|
||||||
|
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final dataController =
|
||||||
|
context.read<FieldEditorBloc>().typeOptionController;
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// TODO(yijing): improve hint text
|
||||||
|
PropertyTitle(LocaleKeys.settings_user_name.tr()),
|
||||||
|
BlocSelector<FieldEditorBloc, FieldEditorState, String>(
|
||||||
|
selector: (state) => state.field.name,
|
||||||
|
builder: (context, fieldName) => MobileFieldNameTextField(
|
||||||
|
text: fieldName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: PropertyTitle(
|
||||||
|
LocaleKeys.grid_field_visibility.tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Toggle(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
value: _visibility.isVisibleState(),
|
||||||
|
style: ToggleStyle.mobile,
|
||||||
|
onChanged: (newValue) {
|
||||||
|
final newVisibility = _visibility.toggle();
|
||||||
|
|
||||||
|
context.read<DatabasePropertyBloc>().add(
|
||||||
|
DatabasePropertyEvent.setFieldVisibility(
|
||||||
|
widget.fieldInfo.id,
|
||||||
|
newVisibility,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
setState(() => _visibility = newVisibility);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const VSpace(8),
|
||||||
|
if (!typeOptionLoader.field.isPrimary)
|
||||||
|
MobileFieldTypeOptionEditor(
|
||||||
|
dataController: dataController,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,17 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/mobile/presentation/widgets/flowy_paginated_bottom_sheet.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/filter/filter_menu_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/filter/filter_menu_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/sort/sort_menu_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/sort/sort_menu_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/setting/database_settings_list.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class MobileGridSettingButton extends StatelessWidget {
|
class MobileDatabaseSettingsButton extends StatelessWidget {
|
||||||
const MobileGridSettingButton({
|
const MobileDatabaseSettingsButton({
|
||||||
super.key,
|
super.key,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required this.toggleExtension,
|
required this.toggleExtension,
|
||||||
@ -56,8 +60,7 @@ class MobileGridSettingButton extends StatelessWidget {
|
|||||||
width: 24,
|
width: 24,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
// TODO(Xazin): Database Settings
|
onPressed: () => _showMobileSettings(context, controller),
|
||||||
onPressed: () {},
|
|
||||||
icon: const FlowySvg(
|
icon: const FlowySvg(
|
||||||
FlowySvgs.m_setting_m,
|
FlowySvgs.m_setting_m,
|
||||||
size: Size.square(24),
|
size: Size.square(24),
|
||||||
@ -69,4 +72,18 @@ class MobileGridSettingButton extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showMobileSettings(
|
||||||
|
BuildContext context,
|
||||||
|
DatabaseController controller,
|
||||||
|
) =>
|
||||||
|
showPaginatedBottomSheet(
|
||||||
|
context,
|
||||||
|
page: SheetPage(
|
||||||
|
title: LocaleKeys.settings_title.tr(),
|
||||||
|
body: DatabaseSettingsList(
|
||||||
|
databaseController: controller,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
@ -1,27 +1,24 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart';
|
import 'package:appflowy/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/group/database_group.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/setting/database_settings_list.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/size.dart';
|
import 'package:flowy_infra/size.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../grid/presentation/layout/sizes.dart';
|
|
||||||
import '../../grid/presentation/widgets/toolbar/grid_layout.dart';
|
|
||||||
import 'setting_property_list.dart';
|
|
||||||
|
|
||||||
class SettingButton extends StatefulWidget {
|
class SettingButton extends StatefulWidget {
|
||||||
final DatabaseController databaseController;
|
|
||||||
const SettingButton({
|
const SettingButton({
|
||||||
|
super.key,
|
||||||
required this.databaseController,
|
required this.databaseController,
|
||||||
Key? key,
|
});
|
||||||
}) : super(key: key);
|
|
||||||
|
final DatabaseController databaseController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SettingButton> createState() => _SettingButtonState();
|
State<SettingButton> createState() => _SettingButtonState();
|
||||||
@ -47,193 +44,27 @@ class _SettingButtonState extends State<SettingButton> {
|
|||||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||||
padding: GridSize.toolbarSettingButtonInsets,
|
padding: GridSize.toolbarSettingButtonInsets,
|
||||||
radius: Corners.s4Border,
|
radius: Corners.s4Border,
|
||||||
onPressed: () => _popoverController.show(),
|
onPressed: _popoverController.show,
|
||||||
|
),
|
||||||
|
popupBuilder: (BuildContext context) => DatabaseSettingsList(
|
||||||
|
databaseController: widget.databaseController,
|
||||||
),
|
),
|
||||||
popupBuilder: (BuildContext context) {
|
|
||||||
return DatabaseSettingListPopover(
|
|
||||||
databaseController: widget.databaseController,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DatabaseSettingListPopover extends StatefulWidget {
|
|
||||||
final DatabaseController databaseController;
|
|
||||||
|
|
||||||
const DatabaseSettingListPopover({
|
|
||||||
required this.databaseController,
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => _DatabaseSettingListPopoverState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DatabaseSettingListPopoverState
|
|
||||||
extends State<DatabaseSettingListPopover> {
|
|
||||||
late final PopoverMutex popoverMutex;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
popoverMutex = PopoverMutex();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final cells =
|
|
||||||
actionsForDatabaseLayout(widget.databaseController.databaseLayout)
|
|
||||||
.map(
|
|
||||||
(action) => action.build(
|
|
||||||
context,
|
|
||||||
widget.databaseController,
|
|
||||||
popoverMutex,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
return ListView.separated(
|
|
||||||
shrinkWrap: true,
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
controller: ScrollController(),
|
|
||||||
itemCount: cells.length,
|
|
||||||
separatorBuilder: (context, index) =>
|
|
||||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
|
||||||
physics: StyledScrollPhysics(),
|
|
||||||
itemBuilder: (BuildContext context, int index) {
|
|
||||||
return cells[index];
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ICalendarSettingImpl extends ICalendarSetting {
|
class ICalendarSettingImpl extends ICalendarSetting {
|
||||||
|
const ICalendarSettingImpl(this._databaseController);
|
||||||
|
|
||||||
final DatabaseController _databaseController;
|
final DatabaseController _databaseController;
|
||||||
|
|
||||||
ICalendarSettingImpl(this._databaseController);
|
@override
|
||||||
|
void updateLayoutSettings(CalendarLayoutSettingPB layoutSettings) =>
|
||||||
|
_databaseController.updateLayoutSetting(
|
||||||
|
calendarLayoutSetting: layoutSettings,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void updateLayoutSettings(CalendarLayoutSettingPB layoutSettings) {
|
CalendarLayoutSettingPB? getLayoutSetting() =>
|
||||||
_databaseController.updateLayoutSetting(
|
_databaseController.databaseLayoutSetting?.calendar;
|
||||||
calendarLayoutSetting: layoutSettings,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CalendarLayoutSettingPB? getLayoutSetting() {
|
|
||||||
return _databaseController.databaseLayoutSetting?.calendar;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum DatabaseSettingAction {
|
|
||||||
showProperties,
|
|
||||||
showLayout,
|
|
||||||
showGroup,
|
|
||||||
showCalendarLayout,
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DatabaseSettingActionExtension on DatabaseSettingAction {
|
|
||||||
FlowySvgData iconData() {
|
|
||||||
switch (this) {
|
|
||||||
case DatabaseSettingAction.showProperties:
|
|
||||||
return FlowySvgs.properties_s;
|
|
||||||
case DatabaseSettingAction.showLayout:
|
|
||||||
return FlowySvgs.database_layout_m;
|
|
||||||
case DatabaseSettingAction.showGroup:
|
|
||||||
return FlowySvgs.group_s;
|
|
||||||
case DatabaseSettingAction.showCalendarLayout:
|
|
||||||
return FlowySvgs.calendar_layout_m;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String title() {
|
|
||||||
switch (this) {
|
|
||||||
case DatabaseSettingAction.showProperties:
|
|
||||||
return LocaleKeys.grid_settings_properties.tr();
|
|
||||||
case DatabaseSettingAction.showLayout:
|
|
||||||
return LocaleKeys.grid_settings_databaseLayout.tr();
|
|
||||||
case DatabaseSettingAction.showGroup:
|
|
||||||
return LocaleKeys.grid_settings_group.tr();
|
|
||||||
case DatabaseSettingAction.showCalendarLayout:
|
|
||||||
return LocaleKeys.calendar_settings_name.tr();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget build(
|
|
||||||
BuildContext context,
|
|
||||||
DatabaseController databaseController,
|
|
||||||
PopoverMutex popoverMutex,
|
|
||||||
) {
|
|
||||||
final popover = switch (this) {
|
|
||||||
DatabaseSettingAction.showLayout => DatabaseLayoutList(
|
|
||||||
viewId: databaseController.viewId,
|
|
||||||
currentLayout: databaseController.databaseLayout,
|
|
||||||
),
|
|
||||||
DatabaseSettingAction.showGroup => DatabaseGroupList(
|
|
||||||
viewId: databaseController.viewId,
|
|
||||||
databaseController: databaseController,
|
|
||||||
onDismissed: () {},
|
|
||||||
),
|
|
||||||
DatabaseSettingAction.showProperties => DatabasePropertyList(
|
|
||||||
viewId: databaseController.viewId,
|
|
||||||
fieldController: databaseController.fieldController,
|
|
||||||
),
|
|
||||||
DatabaseSettingAction.showCalendarLayout => CalendarLayoutSetting(
|
|
||||||
viewId: databaseController.viewId,
|
|
||||||
fieldController: databaseController.fieldController,
|
|
||||||
calendarSettingController: ICalendarSettingImpl(
|
|
||||||
databaseController,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
return AppFlowyPopover(
|
|
||||||
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
|
||||||
direction: PopoverDirection.leftWithTopAligned,
|
|
||||||
mutex: popoverMutex,
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
offset: const Offset(-14, 0),
|
|
||||||
child: SizedBox(
|
|
||||||
height: GridSize.popoverItemHeight,
|
|
||||||
child: FlowyButton(
|
|
||||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
|
||||||
text: FlowyText.medium(
|
|
||||||
title(),
|
|
||||||
color: AFThemeExtension.of(context).textColor,
|
|
||||||
),
|
|
||||||
leftIcon: FlowySvg(
|
|
||||||
iconData(),
|
|
||||||
color: Theme.of(context).iconTheme.color,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
popupBuilder: (context) => popover,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the list of actions that should be shown for the given database layout.
|
|
||||||
List<DatabaseSettingAction> actionsForDatabaseLayout(DatabaseLayoutPB? layout) {
|
|
||||||
switch (layout) {
|
|
||||||
case DatabaseLayoutPB.Board:
|
|
||||||
return [
|
|
||||||
DatabaseSettingAction.showProperties,
|
|
||||||
DatabaseSettingAction.showLayout,
|
|
||||||
DatabaseSettingAction.showGroup,
|
|
||||||
];
|
|
||||||
case DatabaseLayoutPB.Calendar:
|
|
||||||
return [
|
|
||||||
DatabaseSettingAction.showProperties,
|
|
||||||
DatabaseSettingAction.showLayout,
|
|
||||||
DatabaseSettingAction.showCalendarLayout,
|
|
||||||
];
|
|
||||||
case DatabaseLayoutPB.Grid:
|
|
||||||
return [
|
|
||||||
DatabaseSettingAction.showProperties,
|
|
||||||
DatabaseSettingAction.showLayout,
|
|
||||||
];
|
|
||||||
default:
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,23 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/mobile/presentation/widgets/flowy_paginated_bottom_sheet.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
|
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/setting/field_visibility_extension.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/setting/mobile_database_property_editor.dart';
|
||||||
|
import 'package:appflowy/util/platform_extension.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -32,26 +40,45 @@ class DatabasePropertyList extends StatefulWidget {
|
|||||||
|
|
||||||
class _DatabasePropertyListState extends State<DatabasePropertyList> {
|
class _DatabasePropertyListState extends State<DatabasePropertyList> {
|
||||||
final PopoverMutex _popoverMutex = PopoverMutex();
|
final PopoverMutex _popoverMutex = PopoverMutex();
|
||||||
|
late final DatabasePropertyBloc _bloc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_bloc = DatabasePropertyBloc(
|
||||||
|
viewId: widget.viewId,
|
||||||
|
fieldController: widget.fieldController,
|
||||||
|
)..add(const DatabasePropertyEvent.initial());
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider<DatabasePropertyBloc>.value(
|
||||||
create: (context) => DatabasePropertyBloc(
|
value: _bloc,
|
||||||
viewId: widget.viewId,
|
|
||||||
fieldController: widget.fieldController,
|
|
||||||
)..add(const DatabasePropertyEvent.initial()),
|
|
||||||
child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>(
|
child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final cells = state.fieldContexts.mapIndexed((index, field) {
|
final cells = state.fieldContexts
|
||||||
return DatabasePropertyCell(
|
.mapIndexed(
|
||||||
key: ValueKey(field.id),
|
(index, field) => DatabasePropertyCell(
|
||||||
viewId: widget.viewId,
|
key: ValueKey(field.id),
|
||||||
fieldController: widget.fieldController,
|
viewId: widget.viewId,
|
||||||
fieldInfo: field,
|
fieldController: widget.fieldController,
|
||||||
popoverMutex: _popoverMutex,
|
fieldInfo: field,
|
||||||
index: index,
|
popoverMutex: _popoverMutex,
|
||||||
|
index: index,
|
||||||
|
bloc: _bloc,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (PlatformExtension.isMobile) {
|
||||||
|
return ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: cells.length,
|
||||||
|
itemBuilder: (_, index) => cells[index],
|
||||||
|
separatorBuilder: (_, __) => const VSpace(8),
|
||||||
);
|
);
|
||||||
}).toList();
|
}
|
||||||
|
|
||||||
return ReorderableListView(
|
return ReorderableListView(
|
||||||
proxyDecorator: (child, index, _) => Material(
|
proxyDecorator: (child, index, _) => Material(
|
||||||
@ -90,13 +117,7 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
class DatabasePropertyCell extends StatefulWidget {
|
class DatabasePropertyCell extends StatelessWidget {
|
||||||
final FieldController fieldController;
|
|
||||||
final FieldInfo fieldInfo;
|
|
||||||
final String viewId;
|
|
||||||
final PopoverMutex popoverMutex;
|
|
||||||
final int index;
|
|
||||||
|
|
||||||
const DatabasePropertyCell({
|
const DatabasePropertyCell({
|
||||||
super.key,
|
super.key,
|
||||||
required this.fieldInfo,
|
required this.fieldInfo,
|
||||||
@ -104,13 +125,145 @@ class DatabasePropertyCell extends StatefulWidget {
|
|||||||
required this.popoverMutex,
|
required this.popoverMutex,
|
||||||
required this.index,
|
required this.index,
|
||||||
required this.fieldController,
|
required this.fieldController,
|
||||||
|
required this.bloc,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final FieldInfo fieldInfo;
|
||||||
|
final String viewId;
|
||||||
|
final PopoverMutex popoverMutex;
|
||||||
|
final int index;
|
||||||
|
final FieldController fieldController;
|
||||||
|
final DatabasePropertyBloc bloc;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DatabasePropertyCell> createState() => _DatabasePropertyCellState();
|
Widget build(BuildContext context) {
|
||||||
|
if (PlatformExtension.isMobile) {
|
||||||
|
return MobileDatabasePropertyCell(
|
||||||
|
fieldInfo: fieldInfo,
|
||||||
|
viewId: viewId,
|
||||||
|
fieldController: fieldController,
|
||||||
|
bloc: bloc,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DesktopDatabasePropertyCell(
|
||||||
|
fieldInfo: fieldInfo,
|
||||||
|
viewId: viewId,
|
||||||
|
popoverMutex: popoverMutex,
|
||||||
|
index: index,
|
||||||
|
fieldController: fieldController,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DatabasePropertyCellState extends State<DatabasePropertyCell> {
|
class MobileDatabasePropertyCell extends StatefulWidget {
|
||||||
|
const MobileDatabasePropertyCell({
|
||||||
|
super.key,
|
||||||
|
required this.fieldInfo,
|
||||||
|
required this.viewId,
|
||||||
|
required this.fieldController,
|
||||||
|
required this.bloc,
|
||||||
|
});
|
||||||
|
|
||||||
|
final FieldInfo fieldInfo;
|
||||||
|
final String viewId;
|
||||||
|
final FieldController fieldController;
|
||||||
|
final DatabasePropertyBloc bloc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MobileDatabasePropertyCell> createState() =>
|
||||||
|
_MobileDatabasePropertyCellState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MobileDatabasePropertyCellState
|
||||||
|
extends State<MobileDatabasePropertyCell> {
|
||||||
|
late bool isVisible = widget.fieldInfo.visibility?.isVisibleState() ?? false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Theme.of(context).dividerColor),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
onTap: () => FlowyBottomSheetController.of(context)!.push(
|
||||||
|
SheetPage(
|
||||||
|
title: LocaleKeys.grid_field_editProperty.tr(),
|
||||||
|
body: MobileDatabasePropertyEditor(
|
||||||
|
viewId: widget.viewId,
|
||||||
|
fieldInfo: widget.fieldInfo,
|
||||||
|
fieldController: widget.fieldController,
|
||||||
|
bloc: widget.bloc,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
FlowySvg(
|
||||||
|
widget.fieldInfo.fieldType.icon(),
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
size: const Size.square(24),
|
||||||
|
),
|
||||||
|
const HSpace(8),
|
||||||
|
FlowyText.medium(
|
||||||
|
widget.fieldInfo.name,
|
||||||
|
color: AFThemeExtension.of(context).textColor,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
// Toggle Visibility
|
||||||
|
Toggle(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
value: isVisible,
|
||||||
|
style: ToggleStyle.mobile,
|
||||||
|
onChanged: (newValue) {
|
||||||
|
final newVisibility = widget.fieldInfo.visibility!.toggle();
|
||||||
|
|
||||||
|
context.read<DatabasePropertyBloc>().add(
|
||||||
|
DatabasePropertyEvent.setFieldVisibility(
|
||||||
|
widget.fieldInfo.id,
|
||||||
|
newVisibility,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
setState(() => isVisible = !newValue);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
class DesktopDatabasePropertyCell extends StatefulWidget {
|
||||||
|
const DesktopDatabasePropertyCell({
|
||||||
|
super.key,
|
||||||
|
required this.fieldController,
|
||||||
|
required this.fieldInfo,
|
||||||
|
required this.viewId,
|
||||||
|
required this.popoverMutex,
|
||||||
|
required this.index,
|
||||||
|
});
|
||||||
|
|
||||||
|
final FieldController fieldController;
|
||||||
|
final FieldInfo fieldInfo;
|
||||||
|
final String viewId;
|
||||||
|
final PopoverMutex popoverMutex;
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DesktopDatabasePropertyCell> createState() =>
|
||||||
|
_DesktopDatabasePropertyCellState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DesktopDatabasePropertyCellState
|
||||||
|
extends State<DesktopDatabasePropertyCell> {
|
||||||
final PopoverController _popoverController = PopoverController();
|
final PopoverController _popoverController = PopoverController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -173,9 +326,8 @@ class _DatabasePropertyCellState extends State<DatabasePropertyCell> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final newVisiblity = _newFieldVisibility(
|
final newVisiblity =
|
||||||
widget.fieldInfo.fieldSettings!.visibility,
|
widget.fieldInfo.fieldSettings!.visibility.toggle();
|
||||||
);
|
|
||||||
context.read<DatabasePropertyBloc>().add(
|
context.read<DatabasePropertyBloc>().add(
|
||||||
DatabasePropertyEvent.setFieldVisibility(
|
DatabasePropertyEvent.setFieldVisibility(
|
||||||
widget.fieldInfo.id,
|
widget.fieldInfo.id,
|
||||||
@ -197,12 +349,4 @@ class _DatabasePropertyCellState extends State<DatabasePropertyCell> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldVisibility _newFieldVisibility(FieldVisibility current) {
|
|
||||||
return switch (current) {
|
|
||||||
FieldVisibility.AlwaysShown => FieldVisibility.AlwaysHidden,
|
|
||||||
FieldVisibility.AlwaysHidden => FieldVisibility.AlwaysShown,
|
|
||||||
_ => FieldVisibility.AlwaysHidden,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ class Toggle extends StatelessWidget {
|
|||||||
final EdgeInsets padding;
|
final EdgeInsets padding;
|
||||||
|
|
||||||
const Toggle({
|
const Toggle({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
required this.style,
|
required this.style,
|
||||||
@ -20,7 +20,7 @@ class Toggle extends StatelessWidget {
|
|||||||
this.activeBackgroundColor,
|
this.activeBackgroundColor,
|
||||||
this.inactiveBackgroundColor,
|
this.inactiveBackgroundColor,
|
||||||
this.padding = const EdgeInsets.all(8.0),
|
this.padding = const EdgeInsets.all(8.0),
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -28,7 +28,7 @@ class Toggle extends StatelessWidget {
|
|||||||
? activeBackgroundColor ?? Theme.of(context).colorScheme.primary
|
? activeBackgroundColor ?? Theme.of(context).colorScheme.primary
|
||||||
: activeBackgroundColor ?? AFThemeExtension.of(context).toggleOffFill;
|
: activeBackgroundColor ?? AFThemeExtension.of(context).toggleOffFill;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: (() => onChanged(value)),
|
onTap: () => onChanged(value),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
|
@ -15,4 +15,7 @@ class ToggleStyle {
|
|||||||
|
|
||||||
static ToggleStyle get small =>
|
static ToggleStyle get small =>
|
||||||
ToggleStyle(height: 10, width: 16, thumbRadius: 8);
|
ToggleStyle(height: 10, width: 16, thumbRadius: 8);
|
||||||
|
|
||||||
|
static ToggleStyle get mobile =>
|
||||||
|
ToggleStyle(height: 24, width: 42, thumbRadius: 18);
|
||||||
}
|
}
|
||||||
|
@ -424,7 +424,9 @@
|
|||||||
"privacyPolicy": "Privacy Policy",
|
"privacyPolicy": "Privacy Policy",
|
||||||
"userAgreement": "User Agreement",
|
"userAgreement": "User Agreement",
|
||||||
"userprofileError": "Failed to load user profile",
|
"userprofileError": "Failed to load user profile",
|
||||||
"userprofileErrorDescription": "Please try to log out and log back in to check if the issue still persists."
|
"userprofileErrorDescription": "Please try to log out and log back in to check if the issue still persists.",
|
||||||
|
"selectLayout": "Select layout",
|
||||||
|
"selectStartingDay": "Select starting day"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"grid": {
|
"grid": {
|
||||||
@ -852,7 +854,7 @@
|
|||||||
"other": "{} unscheduled events"
|
"other": "{} unscheduled events"
|
||||||
},
|
},
|
||||||
"clickToAdd": "Click to add to the calendar",
|
"clickToAdd": "Click to add to the calendar",
|
||||||
"name": "Calendar layout"
|
"name": "Calendar settings"
|
||||||
},
|
},
|
||||||
"referencedCalendarPrefix": "View of",
|
"referencedCalendarPrefix": "View of",
|
||||||
"quickJumpYear": "Jump to"
|
"quickJumpYear": "Jump to"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user