feat: mobile database settings rework (#4016)

* feat: mobile database settings rework

* chore: clean
This commit is contained in:
Mathias Mogensen
2023-11-27 06:36:10 +02:00
committed by GitHub
parent 929508df16
commit 771dd9979f
25 changed files with 1517 additions and 499 deletions

View File

@ -1,3 +1,5 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.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_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:protobuf/protobuf.dart';
abstract class ICalendarSetting {
const ICalendarSetting();
/// Returns the current layout settings for the calendar view.
CalendarLayoutSettingPB? getLayoutSetting();
@ -42,25 +44,15 @@ class CalendarLayoutSetting extends StatefulWidget {
}
class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
late final PopoverMutex popoverMutex;
@override
void initState() {
popoverMutex = PopoverMutex();
super.initState();
}
late final PopoverMutex popoverMutex = PopoverMutex();
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) {
return CalendarSettingBloc(
viewId: widget.viewId,
layoutSettings: widget.calendarSettingController.getLayoutSetting(),
)..add(
const CalendarSettingEvent.init(),
);
},
create: (context) => CalendarSettingBloc(
viewId: widget.viewId,
layoutSettings: widget.calendarSettingController.getLayoutSetting(),
)..add(const CalendarSettingEvent.init()),
child: BlocBuilder<CalendarSettingBloc, CalendarSettingState>(
builder: (context, state) {
final CalendarLayoutSettingPB? settings = state.layoutSetting
@ -69,39 +61,34 @@ class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
if (settings == null) {
return const CircularProgressIndicator();
}
final availableSettings = _availableCalendarSettings(settings);
final items = availableSettings.map((setting) {
switch (setting) {
case CalendarLayoutSettingAction.showWeekNumber:
return ShowWeekNumber(
showWeekNumbers: settings.showWeekNumbers,
onUpdated: (showWeekNumbers) {
_updateLayoutSettings(
context,
showWeekNumbers: showWeekNumbers,
);
},
onUpdated: (showWeekNumbers) => _updateLayoutSettings(
context,
showWeekNumbers: showWeekNumbers,
),
);
case CalendarLayoutSettingAction.showWeekends:
return ShowWeekends(
showWeekends: settings.showWeekends,
onUpdated: (showWeekends) {
_updateLayoutSettings(
context,
showWeekends: showWeekends,
);
},
onUpdated: (showWeekends) => _updateLayoutSettings(
context,
showWeekends: showWeekends,
),
);
case CalendarLayoutSettingAction.firstDayOfWeek:
return FirstDayOfWeek(
firstDayOfWeek: settings.firstDayOfWeek,
popoverMutex: popoverMutex,
onUpdated: (firstDayOfWeek) {
_updateLayoutSettings(
context,
firstDayOfWeek: firstDayOfWeek,
);
},
onUpdated: (firstDayOfWeek) => _updateLayoutSettings(
context,
firstDayOfWeek: firstDayOfWeek,
),
);
case CalendarLayoutSettingAction.layoutField:
return LayoutDateField(
@ -109,15 +96,13 @@ class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
viewId: widget.viewId,
fieldId: settings.fieldId,
popoverMutex: popoverMutex,
onUpdated: (fieldId) {
_updateLayoutSettings(
context,
layoutFieldId: fieldId,
);
},
onUpdated: (fieldId) => _updateLayoutSettings(
context,
layoutFieldId: fieldId,
),
);
default:
return const SizedBox();
return const SizedBox.shrink();
}
}).toList();
@ -127,10 +112,10 @@ class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
shrinkWrap: true,
controller: ScrollController(),
itemCount: items.length,
separatorBuilder: (context, index) =>
separatorBuilder: (_, __) =>
VSpace(GridSize.typeOptionSeparatorHeight),
physics: StyledScrollPhysics(),
itemBuilder: (BuildContext context, int index) => items[index],
itemBuilder: (_, int index) => items[index],
padding: const EdgeInsets.all(6.0),
),
);
@ -144,28 +129,14 @@ class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
) {
final List<CalendarLayoutSettingAction> settings = [
CalendarLayoutSettingAction.layoutField,
// CalendarLayoutSettingAction.layoutType,
// CalendarLayoutSettingAction.showWeekNumber,
];
switch (layoutSettings.layoutTy) {
case CalendarLayoutPB.DayLayout:
// settings.add(CalendarLayoutSettingAction.showTimeLine);
break;
case CalendarLayoutPB.MonthLayout:
settings.addAll([
// CalendarLayoutSettingAction.showWeekends,
// if (layoutSettings.showWeekends)
CalendarLayoutSettingAction.firstDayOfWeek,
]);
break;
case CalendarLayoutPB.WeekLayout:
settings.addAll([
// CalendarLayoutSettingAction.showWeekends,
// if (layoutSettings.showWeekends)
CalendarLayoutSettingAction.firstDayOfWeek,
// CalendarLayoutSettingAction.showTimeLine,
]);
settings.add(CalendarLayoutSettingAction.firstDayOfWeek);
break;
}
@ -189,16 +160,20 @@ class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
if (showWeekends != null) {
setting.showWeekends = !showWeekends;
}
if (showWeekNumbers != null) {
setting.showWeekNumbers = !showWeekNumbers;
}
if (firstDayOfWeek != null) {
setting.firstDayOfWeek = firstDayOfWeek;
}
if (layoutFieldId != null) {
setting.fieldId = layoutFieldId;
}
});
context
.read<CalendarSettingBloc>()
.add(CalendarSettingEvent.updateLayoutSetting(setting));
@ -208,21 +183,21 @@ class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
}
class LayoutDateField extends StatelessWidget {
final String fieldId;
final String viewId;
final FieldController fieldController;
final PopoverMutex popoverMutex;
final Function(String fieldId) onUpdated;
const LayoutDateField({
super.key,
required this.fieldId,
required this.fieldController,
required this.viewId,
required this.popoverMutex,
required this.onUpdated,
super.key,
});
final String fieldId;
final String viewId;
final FieldController fieldController;
final PopoverMutex popoverMutex;
final Function(String fieldId) onUpdated;
@override
Widget build(BuildContext context) {
return AppFlowyPopover(
@ -264,8 +239,8 @@ class LayoutDateField extends StatelessWidget {
width: 200,
child: ListView.separated(
shrinkWrap: true,
itemBuilder: (context, index) => items[index],
separatorBuilder: (context, index) =>
itemBuilder: (_, index) => items[index],
separatorBuilder: (_, __) =>
VSpace(GridSize.typeOptionSeparatorHeight),
itemCount: items.length,
),
@ -288,21 +263,19 @@ class LayoutDateField extends StatelessWidget {
}
class ShowWeekNumber extends StatelessWidget {
final bool showWeekNumbers;
final Function(bool showWeekNumbers) onUpdated;
const ShowWeekNumber({
super.key,
required this.showWeekNumbers,
required this.onUpdated,
super.key,
});
final bool showWeekNumbers;
final Function(bool showWeekNumbers) onUpdated;
@override
Widget build(BuildContext context) {
return _toggleItem(
onToggle: (showWeekNumbers) {
onUpdated(!showWeekNumbers);
},
onToggle: (showWeekNumbers) => onUpdated(!showWeekNumbers),
value: showWeekNumbers,
text: LocaleKeys.calendar_settings_showWeekNumbers.tr(),
);
@ -310,20 +283,19 @@ class ShowWeekNumber extends StatelessWidget {
}
class ShowWeekends extends StatelessWidget {
final bool showWeekends;
final Function(bool showWeekends) onUpdated;
const ShowWeekends({
super.key,
required this.showWeekends,
required this.onUpdated,
});
final bool showWeekends;
final Function(bool showWeekends) onUpdated;
@override
Widget build(BuildContext context) {
return _toggleItem(
onToggle: (showWeekends) {
onUpdated(!showWeekends);
},
onToggle: (showWeekends) => onUpdated(!showWeekends),
value: showWeekends,
text: LocaleKeys.calendar_settings_showWeekends.tr(),
);
@ -331,16 +303,17 @@ class ShowWeekends extends StatelessWidget {
}
class FirstDayOfWeek extends StatelessWidget {
final int firstDayOfWeek;
final PopoverMutex popoverMutex;
final Function(int firstDayOfWeek) onUpdated;
const FirstDayOfWeek({
super.key,
required this.firstDayOfWeek,
required this.onUpdated,
required this.popoverMutex,
required this.onUpdated,
});
final int firstDayOfWeek;
final PopoverMutex popoverMutex;
final Function(int firstDayOfWeek) onUpdated;
@override
Widget build(BuildContext context) {
return AppFlowyPopover(
@ -370,8 +343,8 @@ class FirstDayOfWeek extends StatelessWidget {
width: 100,
child: ListView.separated(
shrinkWrap: true,
itemBuilder: (context, index) => items[index],
separatorBuilder: (context, index) =>
itemBuilder: (_, index) => items[index],
separatorBuilder: (_, __) =>
VSpace(GridSize.typeOptionSeparatorHeight),
itemCount: len,
),
@ -425,18 +398,19 @@ enum CalendarLayoutSettingAction {
}
class StartFromButton extends StatelessWidget {
final int dayIndex;
final String title;
final bool isSelected;
final void Function(int) onTap;
const StartFromButton({
super.key,
required this.title,
required this.dayIndex,
required this.onTap,
required this.isSelected,
super.key,
});
final String title;
final int dayIndex;
final void Function(int) onTap;
final bool isSelected;
@override
Widget build(BuildContext context) {
return SizedBox(

View File

@ -28,7 +28,7 @@ import 'widgets/footer/grid_footer.dart';
import 'widgets/header/grid_header.dart';
import 'widgets/row/row.dart';
import 'widgets/shortcuts.dart';
import 'widgets/toolbar/mobile_grid_setting.dart';
import '../../widgets/setting/mobile_database_settings_button.dart';
class MobileGridTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
final _toggleExtension = ToggleExtensionNotifier();
@ -49,7 +49,7 @@ class MobileGridTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
@override
Widget settingBar(BuildContext context, DatabaseController controller) {
return MobileGridSettingButton(
return MobileDatabaseSettingsButton(
key: _makeValueKey(controller),
controller: controller,
toggleExtension: _toggleExtension,

View File

@ -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),
);
}
}

View File

@ -1,6 +1,6 @@
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/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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -34,7 +34,7 @@ class MobileTabBarHeader extends StatelessWidget {
overflow: TextOverflow.ellipsis,
),
),
MobileGridSettingButton(
MobileDatabaseSettingsButton(
controller: state
.tabBarControllerByViewId[currentView.viewId]!
.controller,

View File

@ -22,15 +22,16 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:protobuf/protobuf.dart' hide FieldInfo;
class DatabaseGroupList extends StatelessWidget {
final String viewId;
final DatabaseController databaseController;
final VoidCallback onDismissed;
const DatabaseGroupList({
super.key,
required this.viewId,
required this.databaseController,
required this.onDismissed,
Key? key,
}) : super(key: key);
});
final String viewId;
final DatabaseController databaseController;
final VoidCallback onDismissed;
@override
Widget build(BuildContext context) {

View File

@ -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),
);
}
}

View File

@ -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,
};
}

View File

@ -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 [];
}
}

View File

@ -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,
};
}

View File

@ -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);
},
),
);
}
}

View File

@ -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,
),
],
),
);
},
);
},
),
);
}
}

View File

@ -1,13 +1,17 @@
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/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/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_bloc/flutter_bloc.dart';
class MobileGridSettingButton extends StatelessWidget {
const MobileGridSettingButton({
class MobileDatabaseSettingsButton extends StatelessWidget {
const MobileDatabaseSettingsButton({
super.key,
required this.controller,
required this.toggleExtension,
@ -56,8 +60,7 @@ class MobileGridSettingButton extends StatelessWidget {
width: 24,
child: IconButton(
padding: EdgeInsets.zero,
// TODO(Xazin): Database Settings
onPressed: () {},
onPressed: () => _showMobileSettings(context, controller),
icon: const FlowySvg(
FlowySvgs.m_setting_m,
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,
),
),
);
}

View File

@ -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/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/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/setting_entities.pbenum.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.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 {
final DatabaseController databaseController;
const SettingButton({
super.key,
required this.databaseController,
Key? key,
}) : super(key: key);
});
final DatabaseController databaseController;
@override
State<SettingButton> createState() => _SettingButtonState();
@ -47,193 +44,27 @@ class _SettingButtonState extends State<SettingButton> {
hoverColor: AFThemeExtension.of(context).lightGreyHover,
padding: GridSize.toolbarSettingButtonInsets,
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 {
const ICalendarSettingImpl(this._databaseController);
final DatabaseController _databaseController;
ICalendarSettingImpl(this._databaseController);
@override
void updateLayoutSettings(CalendarLayoutSettingPB layoutSettings) =>
_databaseController.updateLayoutSetting(
calendarLayoutSetting: layoutSettings,
);
@override
void updateLayoutSettings(CalendarLayoutSettingPB layoutSettings) {
_databaseController.updateLayoutSetting(
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 [];
}
CalendarLayoutSettingPB? getLayoutSetting() =>
_databaseController.databaseLayoutSetting?.calendar;
}

View File

@ -1,15 +1,23 @@
import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/widgets/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_info.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/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/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_popover/appflowy_popover.dart';
import 'package:collection/collection.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';
@ -32,26 +40,45 @@ class DatabasePropertyList extends StatefulWidget {
class _DatabasePropertyListState extends State<DatabasePropertyList> {
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
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => DatabasePropertyBloc(
viewId: widget.viewId,
fieldController: widget.fieldController,
)..add(const DatabasePropertyEvent.initial()),
return BlocProvider<DatabasePropertyBloc>.value(
value: _bloc,
child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>(
builder: (context, state) {
final cells = state.fieldContexts.mapIndexed((index, field) {
return DatabasePropertyCell(
key: ValueKey(field.id),
viewId: widget.viewId,
fieldController: widget.fieldController,
fieldInfo: field,
popoverMutex: _popoverMutex,
index: index,
final cells = state.fieldContexts
.mapIndexed(
(index, field) => DatabasePropertyCell(
key: ValueKey(field.id),
viewId: widget.viewId,
fieldController: widget.fieldController,
fieldInfo: field,
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(
proxyDecorator: (child, index, _) => Material(
@ -90,13 +117,7 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
}
@visibleForTesting
class DatabasePropertyCell extends StatefulWidget {
final FieldController fieldController;
final FieldInfo fieldInfo;
final String viewId;
final PopoverMutex popoverMutex;
final int index;
class DatabasePropertyCell extends StatelessWidget {
const DatabasePropertyCell({
super.key,
required this.fieldInfo,
@ -104,13 +125,145 @@ class DatabasePropertyCell extends StatefulWidget {
required this.popoverMutex,
required this.index,
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
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();
@override
@ -173,9 +326,8 @@ class _DatabasePropertyCellState extends State<DatabasePropertyCell> {
return;
}
final newVisiblity = _newFieldVisibility(
widget.fieldInfo.fieldSettings!.visibility,
);
final newVisiblity =
widget.fieldInfo.fieldSettings!.visibility.toggle();
context.read<DatabasePropertyBloc>().add(
DatabasePropertyEvent.setFieldVisibility(
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,
};
}
}