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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1517 additions and 499 deletions

View File

@ -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_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/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/tab_bar/desktop/tab_bar_add_button.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_document.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_property_list.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
@ -1138,7 +1140,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
/// Should call [tapDatabaseSettingButton] first.
Future<void> tapViewPropertiesButton() async {
final findSettingItem = find.byType(DatabaseSettingListPopover);
final findSettingItem = find.byType(DatabaseSettingsList);
final findLayoutButton = find.byWidgetPredicate(
(widget) =>
widget is FlowyText &&
@ -1155,7 +1157,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
/// Should call [tapDatabaseSettingButton] first.
Future<void> tapDatabaseLayoutButton() async {
final findSettingItem = find.byType(DatabaseSettingListPopover);
final findSettingItem = find.byType(DatabaseSettingsList);
final findLayoutButton = find.byWidgetPredicate(
(widget) =>
widget is FlowyText &&
@ -1171,7 +1173,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
}
Future<void> tapCalendarLayoutSettingButton() async {
final findSettingItem = find.byType(DatabaseSettingListPopover);
final findSettingItem = find.byType(DatabaseSettingsList);
final findLayoutButton = find.byWidgetPredicate(
(widget) =>
widget is FlowyText &&
@ -1593,7 +1595,8 @@ extension AppFlowyDatabaseTest on WidgetTester {
) async {
final field = find.byWidgetPredicate(
(widget) =>
widget is DatabasePropertyCell && widget.fieldInfo.name == fieldName,
widget is DesktopDatabasePropertyCell &&
widget.fieldInfo.name == fieldName,
);
final toggleVisibilityButton =
find.descendant(of: field, matching: find.byType(FlowyIconButton));

View File

@ -158,9 +158,7 @@ class MobileDBFieldBottomSheetBody extends StatelessWidget {
BottomSheetActionWidget(
svg: FlowySvgs.date_s,
text: LocaleKeys.grid_field_editProperty.tr(),
onTap: () {
onAction(MobileDBBottomSheetGeneralAction.typeOption);
},
onTap: () => onAction(MobileDBBottomSheetGeneralAction.typeOption),
),
const VSpace(8),
Row(
@ -170,9 +168,8 @@ class MobileDBFieldBottomSheetBody extends StatelessWidget {
child: BottomSheetActionWidget(
svg: FlowySvgs.hide_m,
text: LocaleKeys.grid_field_hide.tr(),
onTap: () {
onAction(MobileDBBottomSheetGeneralAction.toggleVisibility);
},
onTap: () =>
onAction(MobileDBBottomSheetGeneralAction.toggleVisibility),
),
),
const HSpace(8),

View File

@ -10,11 +10,12 @@ import 'package:go_router/go_router.dart';
Future<void> showMobileBottomSheet({
required BuildContext context,
required WidgetBuilder builder,
bool isDragEnabled = true,
}) async {
showModalBottomSheet(
context: context,
isScrollControlled: true,
enableDrag: true,
enableDrag: isDragEnabled,
useSafeArea: true,
builder: builder,
);

View File

@ -1,7 +1,6 @@
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/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:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -53,10 +52,6 @@ class _MobileCreateRowFieldScreenState
),
body: MobileFieldEditor(
viewId: widget.viewId,
typeOptionLoader: FieldTypeOptionLoader(
viewId: widget.viewId,
field: widget.typeOption.field_2,
),
fieldController: widget.fieldController,
field: widget.typeOption.field_2,
),

View File

@ -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/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/type_option/type_option_context.dart';
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -57,10 +56,6 @@ class CardPropertyEditScreen extends StatelessWidget {
),
body: MobileFieldEditor(
viewId: cellContext.viewId,
typeOptionLoader: FieldTypeOptionLoader(
viewId: cellContext.viewId,
field: cellContext.fieldInfo.field,
),
fieldController: fieldController,
field: cellContext.fieldInfo.field,
),

View File

@ -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/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/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:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
@ -17,18 +20,21 @@ class MobileFieldEditor extends StatelessWidget {
const MobileFieldEditor({
super.key,
required this.viewId,
required this.typeOptionLoader,
required this.field,
required this.fieldController,
});
final String viewId;
final FieldController fieldController;
final FieldTypeOptionLoader typeOptionLoader;
final FieldPB field;
@override
Widget build(BuildContext context) {
final typeOptionLoader = FieldTypeOptionLoader(
viewId: viewId,
field: field,
);
return BlocProvider(
create: (context) {
return FieldEditorBloc(
@ -49,42 +55,35 @@ class MobileFieldEditor extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// field name
// TODO(yijing): improve hint text
PropertyTitle(LocaleKeys.settings_user_name.tr()),
BlocSelector<FieldEditorBloc, FieldEditorState, String>(
selector: (state) => state.field.name,
builder: (context, fieldName) {
return MobileFieldNameTextField(
text: fieldName,
);
},
builder: (context, fieldName) =>
MobileFieldNameTextField(text: fieldName),
),
Row(
children: [
Expanded(
child:
PropertyTitle(LocaleKeys.grid_field_visibility.tr()),
child: PropertyTitle(
LocaleKeys.grid_field_visibility.tr(),
),
),
VisibilitySwitch(
isFieldHidden: state.field.visibility ==
FieldVisibility.AlwaysHidden,
onChanged: () {
context.read<RowDetailBloc>().add(
isVisible:
state.field.visibility?.isVisibleState() ?? false,
onChanged: () => context.read<RowDetailBloc>().add(
RowDetailEvent.toggleFieldVisibility(
state.field.id,
),
);
},
),
),
],
),
const VSpace(8),
// edit property type and settings
if (!typeOptionLoader.field.isPrimary)
MobileFieldTypeOptionEditor(
dataController: dataController,
),
MobileFieldTypeOptionEditor(dataController: dataController),
],
),
);
@ -97,11 +96,11 @@ class MobileFieldEditor extends StatelessWidget {
class VisibilitySwitch extends StatefulWidget {
const VisibilitySwitch({
super.key,
required this.isFieldHidden,
required this.isVisible,
this.onChanged,
});
final bool isFieldHidden;
final bool isVisible;
final Function? onChanged;
@override
@ -109,18 +108,17 @@ class VisibilitySwitch extends StatefulWidget {
}
class _VisibilitySwitchState extends State<VisibilitySwitch> {
late bool _isFieldHidden = widget.isFieldHidden;
late bool _isVisible = widget.isVisible;
@override
Widget build(BuildContext context) {
return Switch.adaptive(
activeColor: Theme.of(context).colorScheme.primary,
value: !_isFieldHidden,
onChanged: (bool value) {
setState(() {
_isFieldHidden = !_isFieldHidden;
return Toggle(
padding: EdgeInsets.zero,
value: _isVisible,
style: ToggleStyle.mobile,
onChanged: (newValue) {
widget.onChanged?.call();
});
setState(() => _isVisible = newValue);
},
);
}

View File

@ -5,24 +5,37 @@ class MobileSettingItem extends StatelessWidget {
const MobileSettingItem({
super.key,
required this.name,
this.padding = const EdgeInsets.only(bottom: 4),
this.trailing,
this.leadingIcon,
this.subtitle,
required this.trailing,
this.onTap,
});
final String name;
final EdgeInsets padding;
final Widget? trailing;
final Widget? leadingIcon;
final Widget? subtitle;
final Widget trailing;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 4),
padding: padding,
child: ListTile(
title: FlowyText.medium(
title: Row(
children: [
if (leadingIcon != null) ...[
leadingIcon!,
const HSpace(8),
],
FlowyText.medium(
name,
fontSize: 14.0,
),
],
),
subtitle: subtitle,
trailing: trailing,
onTap: onTap,

View File

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

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(
create: (context) => CalendarSettingBloc(
viewId: widget.viewId,
layoutSettings: widget.calendarSettingController.getLayoutSetting(),
)..add(
const CalendarSettingEvent.init(),
);
},
)..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(
onUpdated: (showWeekNumbers) => _updateLayoutSettings(
context,
showWeekNumbers: showWeekNumbers,
);
},
),
);
case CalendarLayoutSettingAction.showWeekends:
return ShowWeekends(
showWeekends: settings.showWeekends,
onUpdated: (showWeekends) {
_updateLayoutSettings(
onUpdated: (showWeekends) => _updateLayoutSettings(
context,
showWeekends: showWeekends,
);
},
),
);
case CalendarLayoutSettingAction.firstDayOfWeek:
return FirstDayOfWeek(
firstDayOfWeek: settings.firstDayOfWeek,
popoverMutex: popoverMutex,
onUpdated: (firstDayOfWeek) {
_updateLayoutSettings(
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(
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) {
return DatabaseSettingListPopover(
popupBuilder: (BuildContext context) => DatabaseSettingsList(
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) {
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(
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,
);
}
class _DatabasePropertyCellState extends State<DatabasePropertyCell> {
return DesktopDatabasePropertyCell(
fieldInfo: fieldInfo,
viewId: viewId,
popoverMutex: popoverMutex,
index: index,
fieldController: fieldController,
);
}
}
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,
};
}
}

View File

@ -12,7 +12,7 @@ class Toggle extends StatelessWidget {
final EdgeInsets padding;
const Toggle({
Key? key,
super.key,
required this.value,
required this.onChanged,
required this.style,
@ -20,7 +20,7 @@ class Toggle extends StatelessWidget {
this.activeBackgroundColor,
this.inactiveBackgroundColor,
this.padding = const EdgeInsets.all(8.0),
}) : super(key: key);
});
@override
Widget build(BuildContext context) {
@ -28,7 +28,7 @@ class Toggle extends StatelessWidget {
? activeBackgroundColor ?? Theme.of(context).colorScheme.primary
: activeBackgroundColor ?? AFThemeExtension.of(context).toggleOffFill;
return GestureDetector(
onTap: (() => onChanged(value)),
onTap: () => onChanged(value),
child: Padding(
padding: padding,
child: Stack(

View File

@ -15,4 +15,7 @@ class ToggleStyle {
static ToggleStyle get small =>
ToggleStyle(height: 10, width: 16, thumbRadius: 8);
static ToggleStyle get mobile =>
ToggleStyle(height: 24, width: 42, thumbRadius: 18);
}

View File

@ -424,7 +424,9 @@
"privacyPolicy": "Privacy Policy",
"userAgreement": "User Agreement",
"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": {
@ -852,7 +854,7 @@
"other": "{} unscheduled events"
},
"clickToAdd": "Click to add to the calendar",
"name": "Calendar layout"
"name": "Calendar settings"
},
"referencedCalendarPrefix": "View of",
"quickJumpYear": "Jump to"