mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: mobile view editor improvements (#4186)
* feat: database view editor improvements * feat: implement calendar settings * fix: wrong view being opened
This commit is contained in:
parent
4fa08fda02
commit
13260e1db8
@ -142,7 +142,7 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 36),
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 38),
|
||||
builder: (_) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/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/field/field_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -6,6 +7,7 @@ import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'mobile_create_field_screen.dart';
|
||||
import 'mobile_edit_field_screen.dart';
|
||||
import 'mobile_field_picker_list.dart';
|
||||
import 'mobile_field_type_grid.dart';
|
||||
import 'mobile_field_type_option_editor.dart';
|
||||
import 'mobile_quick_field_editor.dart';
|
||||
@ -104,3 +106,22 @@ void showQuickEditField(
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<String?> showFieldPicker(
|
||||
BuildContext context,
|
||||
String? selectedFieldId,
|
||||
FieldController fieldController,
|
||||
bool Function(FieldInfo fieldInfo) filterBy,
|
||||
) {
|
||||
return showMobileBottomSheet<String>(
|
||||
context,
|
||||
padding: EdgeInsets.zero,
|
||||
builder: (context) {
|
||||
return MobileFieldPickerList(
|
||||
selectedFieldId: selectedFieldId,
|
||||
fieldController: fieldController,
|
||||
filterBy: filterBy,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,150 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
|
||||
import 'package:appflowy/plugins/base/drag_handler.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/grid/presentation/widgets/header/field_type_extension.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class MobileFieldPickerList extends StatefulWidget {
|
||||
MobileFieldPickerList({
|
||||
super.key,
|
||||
required this.selectedFieldId,
|
||||
required FieldController fieldController,
|
||||
required bool Function(FieldInfo fieldInfo) filterBy,
|
||||
}) : fields = fieldController.fieldInfos.where(filterBy).toList();
|
||||
|
||||
final String? selectedFieldId;
|
||||
final List<FieldInfo> fields;
|
||||
|
||||
@override
|
||||
State<MobileFieldPickerList> createState() => _MobileFieldPickerListState();
|
||||
}
|
||||
|
||||
class _MobileFieldPickerListState extends State<MobileFieldPickerList> {
|
||||
String? newFieldId;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
newFieldId = widget.selectedFieldId;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const Center(child: DragHandler()),
|
||||
_Header(newFieldId: newFieldId),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: widget.fields.length,
|
||||
itemBuilder: (context, index) => _FieldButton(
|
||||
field: widget.fields[index],
|
||||
showTopBorder: index == 0,
|
||||
isSelected: widget.fields[index].id == newFieldId,
|
||||
onSelect: (fieldId) => setState(() => newFieldId = fieldId),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Header extends StatelessWidget {
|
||||
const _Header({required this.newFieldId});
|
||||
|
||||
final String? newFieldId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox.square(
|
||||
dimension: 36,
|
||||
child: IconButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () => context.pop(),
|
||||
icon: const FlowySvg(
|
||||
FlowySvgs.arrow_left_s,
|
||||
size: Size.square(20),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 4, 8, 8),
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 5,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
elevation: 0,
|
||||
visualDensity: VisualDensity.compact,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
enableFeedback: true,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
onPressed: () => context.pop(newFieldId),
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.button_save.tr(),
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Center(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.calendar_settings_changeLayoutDateField.tr(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FieldButton extends StatelessWidget {
|
||||
const _FieldButton({
|
||||
required this.field,
|
||||
required this.isSelected,
|
||||
required this.onSelect,
|
||||
required this.showTopBorder,
|
||||
});
|
||||
|
||||
final FieldInfo field;
|
||||
final bool isSelected;
|
||||
final void Function(String fieldId) onSelect;
|
||||
final bool showTopBorder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyOptionTile.checkbox(
|
||||
text: field.name,
|
||||
isSelected: isSelected,
|
||||
leftIcon: FlowySvg(
|
||||
field.fieldType.icon(),
|
||||
size: const Size.square(20),
|
||||
),
|
||||
showTopBorder: showTopBorder,
|
||||
onTap: () => onSelect(field.id),
|
||||
);
|
||||
}
|
||||
}
|
@ -134,7 +134,7 @@ class _QuickEditFieldState extends State<QuickEditField> {
|
||||
},
|
||||
),
|
||||
],
|
||||
const VSpace(12),
|
||||
const VSpace(38),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ 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/widgets/header/field_type_extension.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/setting/field_visibility_extension.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -23,11 +24,9 @@ class MobileDatabaseFieldList extends StatelessWidget {
|
||||
const MobileDatabaseFieldList({
|
||||
super.key,
|
||||
required this.databaseController,
|
||||
required this.viewPB,
|
||||
});
|
||||
|
||||
final DatabaseController databaseController;
|
||||
final ViewPB viewPB;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -46,7 +45,7 @@ class MobileDatabaseFieldList extends StatelessWidget {
|
||||
child: SingleChildScrollView(
|
||||
child: _MobileDatabaseFieldListBody(
|
||||
databaseController: databaseController,
|
||||
view: viewPB,
|
||||
view: context.read<ViewBloc>().state.view,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -2,11 +2,16 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/calendar/application/calendar_setting_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../field/mobile_field_bottom_sheets.dart';
|
||||
|
||||
/// [DatabaseViewLayoutPicker] is seen when changing the layout type of a
|
||||
/// database view or creating a new database view.
|
||||
@ -46,6 +51,9 @@ class DatabaseViewLayoutPicker extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// [MobileCalendarViewLayoutSettings] is used when the database layout is
|
||||
/// calendar. It allows changing the field being used to layout the events,
|
||||
/// and which day of the week the calendar starts on.
|
||||
class MobileCalendarViewLayoutSettings extends StatelessWidget {
|
||||
const MobileCalendarViewLayoutSettings({
|
||||
super.key,
|
||||
@ -56,26 +64,36 @@ class MobileCalendarViewLayoutSettings extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<CalendarSettingBloc>(
|
||||
create: (context) {
|
||||
return CalendarSettingBloc(
|
||||
databaseController: databaseController,
|
||||
)..add(const CalendarSettingEvent.initial());
|
||||
},
|
||||
child: BlocBuilder<CalendarSettingBloc, CalendarSettingState>(
|
||||
builder: (context, state) {
|
||||
if (state.layoutSetting == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_layoutField(),
|
||||
_CalendarLayoutField(
|
||||
context: context,
|
||||
databaseController: databaseController,
|
||||
selectedFieldId: state.layoutSetting?.fieldId,
|
||||
),
|
||||
_divider(),
|
||||
..._startWeek(context),
|
||||
..._startWeek(context, state.layoutSetting?.firstDayOfWeek),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _layoutField() {
|
||||
return FlowyOptionTile.text(
|
||||
text: LocaleKeys.calendar_settings_layoutDateField.tr(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _divider() => const VSpace(20);
|
||||
|
||||
List<Widget> _startWeek(BuildContext context) {
|
||||
List<Widget> _startWeek(BuildContext context, int? firstDayOfWeek) {
|
||||
final symbols = DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
|
||||
return [
|
||||
Padding(
|
||||
@ -87,18 +105,85 @@ class MobileCalendarViewLayoutSettings extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
FlowyOptionTile.checkbox(
|
||||
text: symbols.WEEKDAYS[-1],
|
||||
isSelected: true,
|
||||
onTap: () {},
|
||||
text: symbols.WEEKDAYS[0],
|
||||
isSelected: firstDayOfWeek! == 0,
|
||||
onTap: () {
|
||||
context.read<CalendarSettingBloc>().add(
|
||||
const CalendarSettingEvent.updateLayoutSetting(
|
||||
firstDayOfWeek: 0,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
FlowyOptionTile.checkbox(
|
||||
text: symbols.WEEKDAYS[0],
|
||||
isSelected: false,
|
||||
text: symbols.WEEKDAYS[1],
|
||||
isSelected: firstDayOfWeek == 1,
|
||||
showTopBorder: false,
|
||||
onTap: () {},
|
||||
onTap: () {
|
||||
context.read<CalendarSettingBloc>().add(
|
||||
const CalendarSettingEvent.updateLayoutSetting(
|
||||
firstDayOfWeek: 1,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Widget _divider() => const VSpace(20);
|
||||
}
|
||||
|
||||
class _CalendarLayoutField extends StatelessWidget {
|
||||
const _CalendarLayoutField({
|
||||
required this.context,
|
||||
required this.databaseController,
|
||||
required this.selectedFieldId,
|
||||
});
|
||||
|
||||
final BuildContext context;
|
||||
final DatabaseController databaseController;
|
||||
final String? selectedFieldId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
FieldInfo? selectedField;
|
||||
if (selectedFieldId != null) {
|
||||
selectedField =
|
||||
databaseController.fieldController.getField(selectedFieldId!);
|
||||
}
|
||||
return FlowyOptionTile.text(
|
||||
text: LocaleKeys.calendar_settings_layoutDateField.tr(),
|
||||
trailing: selectedFieldId == null
|
||||
? null
|
||||
: Row(
|
||||
children: [
|
||||
FlowyText(
|
||||
selectedField!.name,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
const HSpace(8),
|
||||
const FlowySvg(FlowySvgs.arrow_right_s),
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
final newFieldId = await showFieldPicker(
|
||||
context,
|
||||
selectedFieldId,
|
||||
databaseController.fieldController,
|
||||
(field) => field.fieldType == FieldType.DateTime,
|
||||
);
|
||||
if (context.mounted &&
|
||||
newFieldId != null &&
|
||||
newFieldId != selectedFieldId) {
|
||||
context.read<CalendarSettingBloc>().add(
|
||||
CalendarSettingEvent.updateLayoutSetting(
|
||||
layoutFieldId: newFieldId,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MobileBoardViewLayoutSettings extends StatelessWidget {
|
||||
|
@ -1,30 +1,46 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
|
||||
import 'package:appflowy/plugins/base/drag_handler.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/tab_bar_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'database_view_layout.dart';
|
||||
import 'database_view_quick_actions.dart';
|
||||
|
||||
/// [MobileDatabaseViewList] shows a list of all the views in the database and
|
||||
/// adds a button to create a new database view.
|
||||
class MobileDatabaseViewList extends StatelessWidget {
|
||||
const MobileDatabaseViewList({super.key, required this.views});
|
||||
const MobileDatabaseViewList({super.key, required this.databaseController});
|
||||
|
||||
final List<ViewPB> views;
|
||||
final DatabaseController databaseController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
return BlocBuilder<ViewBloc, ViewState>(
|
||||
builder: (context, state) {
|
||||
final views = [state.view, ...state.view.childViews];
|
||||
final children = [
|
||||
...views.map((view) => MobileDatabaseViewListButton(view: view)),
|
||||
const Center(child: DragHandler()),
|
||||
const _Header(),
|
||||
...views.mapIndexed(
|
||||
(index, view) => MobileDatabaseViewListButton(
|
||||
view: view,
|
||||
databaseController: databaseController,
|
||||
showTopBorder: index == 0,
|
||||
),
|
||||
),
|
||||
const VSpace(20),
|
||||
const MobileNewDatabaseViewButton(),
|
||||
];
|
||||
@ -37,37 +53,124 @@ class MobileDatabaseViewList extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
class MobileDatabaseViewListButton extends StatelessWidget {
|
||||
const MobileDatabaseViewListButton({super.key, required this.view});
|
||||
|
||||
final ViewPB view;
|
||||
class _Header extends StatelessWidget {
|
||||
const _Header();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const iconWidth = 30.0;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 4, 8, 12),
|
||||
child: Stack(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyIconButton(
|
||||
icon: const FlowySvg(
|
||||
FlowySvgs.close_s,
|
||||
size: Size.square(iconWidth),
|
||||
),
|
||||
width: iconWidth,
|
||||
iconPadding: EdgeInsets.zero,
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.grid_settings_viewList.tr(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
class MobileDatabaseViewListButton extends StatelessWidget {
|
||||
const MobileDatabaseViewListButton({
|
||||
super.key,
|
||||
required this.view,
|
||||
required this.databaseController,
|
||||
required this.showTopBorder,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
final DatabaseController databaseController;
|
||||
final bool showTopBorder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<DatabaseTabBarBloc, DatabaseTabBarState>(
|
||||
builder: (context, state) {
|
||||
final index =
|
||||
state.tabBars.indexWhere((tabBar) => tabBar.viewId == view.id);
|
||||
final isSelected = index == state.selectedIndex;
|
||||
return FlowyOptionTile.text(
|
||||
text: view.name,
|
||||
onTap: () {},
|
||||
onTap: () {
|
||||
context
|
||||
.read<DatabaseTabBarBloc>()
|
||||
.add(DatabaseTabBarEvent.selectView(view.id));
|
||||
},
|
||||
leftIcon: _buildViewIconButton(context, view),
|
||||
trailing: FlowySvg(
|
||||
FlowySvgs.three_dots_s,
|
||||
size: const Size.square(20),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
trailing: _trailing(context, isSelected),
|
||||
showTopBorder: showTopBorder,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildViewIconButton(BuildContext context, ViewPB view) {
|
||||
return view.icon.value.isNotEmpty
|
||||
? EmojiText(
|
||||
emoji: view.icon.value,
|
||||
fontSize: 16.0,
|
||||
)
|
||||
: SizedBox.square(
|
||||
return SizedBox.square(
|
||||
dimension: 20.0,
|
||||
child: view.defaultIcon(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _trailing(BuildContext context, bool isSelected) {
|
||||
final more = FlowyIconButton(
|
||||
icon: FlowySvg(
|
||||
FlowySvgs.three_dots_s,
|
||||
size: const Size.square(20),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
onPressed: () {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
padding: EdgeInsets.zero,
|
||||
builder: (_) {
|
||||
return BlocProvider<ViewBloc>(
|
||||
create: (_) =>
|
||||
ViewBloc(view: view)..add(const ViewEvent.initial()),
|
||||
child: MobileDatabaseViewQuickActions(
|
||||
view: view,
|
||||
databaseController: databaseController,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
if (isSelected) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const FlowySvg(
|
||||
FlowySvgs.blue_check_s,
|
||||
size: Size.square(20),
|
||||
blendMode: BlendMode.dst,
|
||||
),
|
||||
const HSpace(8),
|
||||
more,
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return more;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MobileNewDatabaseViewButton extends StatelessWidget {
|
||||
@ -83,12 +186,20 @@ class MobileNewDatabaseViewButton extends StatelessWidget {
|
||||
size: const Size.square(20),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
trailing: FlowySvg(
|
||||
FlowySvgs.three_dots_s,
|
||||
size: const Size.square(20),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
onTap: () {},
|
||||
onTap: () async {
|
||||
final result = await showMobileBottomSheet<(DatabaseLayoutPB, String)>(
|
||||
context,
|
||||
padding: EdgeInsets.zero,
|
||||
builder: (_) {
|
||||
return const MobileCreateDatabaseView();
|
||||
},
|
||||
);
|
||||
if (context.mounted && result != null) {
|
||||
context
|
||||
.read<DatabaseTabBarBloc>()
|
||||
.add(DatabaseTabBarEvent.createView(result.$1, result.$2));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -104,13 +215,13 @@ class MobileCreateDatabaseView extends StatefulWidget {
|
||||
class _MobileCreateDatabaseViewState extends State<MobileCreateDatabaseView> {
|
||||
late final TextEditingController controller;
|
||||
DatabaseLayoutPB layoutType = DatabaseLayoutPB.Grid;
|
||||
String icon = "";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller =
|
||||
TextEditingController(text: LocaleKeys.grid_title_placeholder.tr());
|
||||
controller = TextEditingController(
|
||||
text: LocaleKeys.grid_title_placeholder.tr(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -123,42 +234,93 @@ class _MobileCreateDatabaseViewState extends State<MobileCreateDatabaseView> {
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const Center(child: DragHandler()),
|
||||
_CreateViewHeader(
|
||||
textController: controller,
|
||||
selectedLayout: layoutType,
|
||||
),
|
||||
FlowyOptionTile.textField(
|
||||
controller: controller,
|
||||
leftIcon: _buildViewIcon(),
|
||||
),
|
||||
const VSpace(20),
|
||||
DatabaseViewLayoutPicker(
|
||||
selectedLayout: DatabaseLayoutPB.Grid,
|
||||
onSelect: (layout) {},
|
||||
selectedLayout: layoutType,
|
||||
onSelect: (layout) {
|
||||
setState(() => layoutType = layout);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildViewIcon() {
|
||||
final viewIcon = icon.isNotEmpty
|
||||
? EmojiText(
|
||||
emoji: icon,
|
||||
fontSize: 16.0,
|
||||
)
|
||||
: SizedBox.square(
|
||||
dimension: 18.0,
|
||||
child: FlowySvg(layoutType.icon),
|
||||
);
|
||||
return InkWell(
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
border: Border.fromBorderSide(
|
||||
BorderSide(color: Theme.of(context).dividerColor),
|
||||
class _CreateViewHeader extends StatelessWidget {
|
||||
const _CreateViewHeader({
|
||||
required this.textController,
|
||||
required this.selectedLayout,
|
||||
});
|
||||
|
||||
final TextEditingController textController;
|
||||
final DatabaseLayoutPB selectedLayout;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox.square(
|
||||
dimension: 36,
|
||||
child: IconButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () => context.pop(),
|
||||
icon: const FlowySvg(
|
||||
FlowySvgs.arrow_left_s,
|
||||
size: Size.square(20),
|
||||
),
|
||||
),
|
||||
width: 36,
|
||||
height: 36,
|
||||
child: Center(child: viewIcon),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 4, 8, 8),
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 5,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
elevation: 0,
|
||||
visualDensity: VisualDensity.compact,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
enableFeedback: true,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
onPressed: () {
|
||||
context.pop((selectedLayout, textController.text.trim()));
|
||||
},
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.button_done.tr(),
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Center(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.grid_settings_createView.tr(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,69 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'edit_database_view_screen.dart';
|
||||
|
||||
/// [MobileDatabaseViewQuickActions] is gives users to quickly edit a database
|
||||
/// view from the [MobileDatabaseViewList]
|
||||
class MobileDatabaseViewQuickActions extends StatelessWidget {
|
||||
const MobileDatabaseViewQuickActions({super.key});
|
||||
class MobileDatabaseViewQuickActions extends StatefulWidget {
|
||||
const MobileDatabaseViewQuickActions({
|
||||
super.key,
|
||||
required this.view,
|
||||
required this.databaseController,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
final DatabaseController databaseController;
|
||||
|
||||
@override
|
||||
State<MobileDatabaseViewQuickActions> createState() =>
|
||||
_MobileDatabaseViewQuickActionsState();
|
||||
}
|
||||
|
||||
class _MobileDatabaseViewQuickActionsState
|
||||
extends State<MobileDatabaseViewQuickActions> {
|
||||
bool isEditing = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
return isEditing
|
||||
? MobileEditDatabaseViewScreen(
|
||||
databaseController: widget.databaseController,
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 38),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_actionButton(_Action.edit),
|
||||
_actionButton(context, _Action.edit),
|
||||
_divider(),
|
||||
_actionButton(_Action.duplicate),
|
||||
_actionButton(context, _Action.duplicate),
|
||||
_divider(),
|
||||
_actionButton(_Action.delete),
|
||||
_actionButton(context, _Action.delete),
|
||||
_divider(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _actionButton(_Action action) {
|
||||
return MobileQuickActionButton(
|
||||
Widget _actionButton(BuildContext context, _Action action) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: MobileQuickActionButton(
|
||||
icon: action.icon,
|
||||
text: action.label,
|
||||
onTap: () {},
|
||||
color: action.color(context),
|
||||
onTap: () {
|
||||
if (action == _Action.edit) {
|
||||
setState(() => isEditing = true);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -50,7 +85,7 @@ enum _Action {
|
||||
|
||||
FlowySvgData get icon {
|
||||
return switch (this) {
|
||||
edit => FlowySvgs.grid_s,
|
||||
edit => FlowySvgs.edit_s,
|
||||
duplicate => FlowySvgs.copy_s,
|
||||
delete => FlowySvgs.delete_s,
|
||||
};
|
||||
|
@ -3,13 +3,11 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
|
||||
import 'package:appflowy/plugins/base/drag_handler.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/database_view_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/layout/layout_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -29,11 +27,9 @@ class MobileEditDatabaseViewScreen extends StatefulWidget {
|
||||
const MobileEditDatabaseViewScreen({
|
||||
super.key,
|
||||
required this.databaseController,
|
||||
required this.viewPB,
|
||||
});
|
||||
|
||||
final DatabaseController databaseController;
|
||||
final ViewPB viewPB;
|
||||
|
||||
@override
|
||||
State<MobileEditDatabaseViewScreen> createState() =>
|
||||
@ -52,13 +48,11 @@ class _MobileEditDatabaseViewScreenState
|
||||
return switch (state.currentPage) {
|
||||
MobileEditDatabaseViewPageEnum.main => _EditDatabaseViewMainPage(
|
||||
databaseController: widget.databaseController,
|
||||
viewPB: widget.viewPB,
|
||||
),
|
||||
MobileEditDatabaseViewPageEnum.fields => _wrapSubPage(
|
||||
context,
|
||||
MobileDatabaseFieldList(
|
||||
databaseController: widget.databaseController,
|
||||
viewPB: widget.viewPB,
|
||||
),
|
||||
),
|
||||
_ => const SizedBox.shrink(),
|
||||
@ -84,11 +78,9 @@ class _MobileEditDatabaseViewScreenState
|
||||
class _EditDatabaseViewMainPage extends StatelessWidget {
|
||||
const _EditDatabaseViewMainPage({
|
||||
required this.databaseController,
|
||||
required this.viewPB,
|
||||
});
|
||||
|
||||
final DatabaseController databaseController;
|
||||
final ViewPB viewPB;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -107,7 +99,6 @@ class _EditDatabaseViewMainPage extends StatelessWidget {
|
||||
child: SingleChildScrollView(
|
||||
child: _EditDatabaseViewBody(
|
||||
databaseController: databaseController,
|
||||
view: viewPB,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -157,19 +148,13 @@ class _EditDatabaseViewHeader extends StatelessWidget {
|
||||
class _EditDatabaseViewBody extends StatelessWidget {
|
||||
const _EditDatabaseViewBody({
|
||||
required this.databaseController,
|
||||
required this.view,
|
||||
});
|
||||
|
||||
final DatabaseController databaseController;
|
||||
final ViewPB view;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<ViewBloc>(
|
||||
create: (context) {
|
||||
return ViewBloc(view: view)..add(const ViewEvent.initial());
|
||||
},
|
||||
child: BlocBuilder<ViewBloc, ViewState>(
|
||||
return BlocBuilder<ViewBloc, ViewState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -182,6 +167,12 @@ class _EditDatabaseViewBody extends StatelessWidget {
|
||||
view: state.view,
|
||||
showTopBorder: true,
|
||||
),
|
||||
if (databaseController.databaseLayout == DatabaseLayoutPB.Calendar)
|
||||
DatabaseViewSettingTile(
|
||||
setting: DatabaseViewSettings.calendar,
|
||||
databaseController: databaseController,
|
||||
view: state.view,
|
||||
),
|
||||
DatabaseViewSettingTile(
|
||||
setting: DatabaseViewSettings.fields,
|
||||
databaseController: databaseController,
|
||||
@ -191,7 +182,6 @@ class _EditDatabaseViewBody extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -220,37 +210,9 @@ class _NameAndIconState extends State<_NameAndIcon> {
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyOptionTile.textField(
|
||||
controller: textEditingController,
|
||||
textFieldPadding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
onTextChanged: (text) {
|
||||
context.read<ViewBloc>().add(ViewEvent.rename(text));
|
||||
},
|
||||
leftIcon: _buildViewIcon(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildViewIcon() {
|
||||
final icon = widget.view.icon.value.isNotEmpty
|
||||
? EmojiText(
|
||||
emoji: widget.view.icon.value,
|
||||
fontSize: 16.0,
|
||||
)
|
||||
: SizedBox.square(
|
||||
dimension: 18.0,
|
||||
child: widget.view.defaultIcon(),
|
||||
);
|
||||
return InkWell(
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
border: Border.fromBorderSide(
|
||||
BorderSide(color: Theme.of(context).dividerColor),
|
||||
),
|
||||
),
|
||||
width: 36,
|
||||
height: 36,
|
||||
child: Center(child: icon),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -351,7 +313,8 @@ class DatabaseViewSettingTile extends StatelessWidget {
|
||||
return Row(
|
||||
children: [
|
||||
FlowyText(
|
||||
"$numVisible shown",
|
||||
LocaleKeys.grid_settings_numberOfVisibleFields
|
||||
.tr(args: [numVisible.toString()]),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
const HSpace(8),
|
||||
|
@ -1,227 +0,0 @@
|
||||
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,
|
||||
// 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 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);
|
||||
}
|
||||
}
|
@ -50,8 +50,8 @@ class DatabaseTabBarBloc
|
||||
);
|
||||
}
|
||||
},
|
||||
createView: (layout) {
|
||||
_createLinkedView(layout.layoutType, layout.layoutName);
|
||||
createView: (layout, name) {
|
||||
_createLinkedView(layout.layoutType, name ?? layout.layoutName);
|
||||
},
|
||||
deleteView: (String viewId) async {
|
||||
final result = await ViewBackendService.delete(viewId: viewId);
|
||||
@ -209,8 +209,10 @@ class DatabaseTabBarEvent with _$DatabaseTabBarEvent {
|
||||
List<ViewPB> childViews,
|
||||
) = _DidLoadChildViews;
|
||||
const factory DatabaseTabBarEvent.selectView(String viewId) = _DidSelectView;
|
||||
const factory DatabaseTabBarEvent.createView(DatabaseLayoutPB layout) =
|
||||
_CreateView;
|
||||
const factory DatabaseTabBarEvent.createView(
|
||||
DatabaseLayoutPB layout,
|
||||
String? name,
|
||||
) = _CreateView;
|
||||
const factory DatabaseTabBarEvent.renameView(String viewId, String newName) =
|
||||
_RenameView;
|
||||
const factory DatabaseTabBarEvent.deleteView(String viewId) = _DeleteView;
|
||||
|
@ -1,9 +1,10 @@
|
||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/layout/layout_setting_listener.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
part 'calendar_setting_bloc.freezed.dart';
|
||||
|
||||
@ -11,29 +12,83 @@ typedef DayOfWeek = int;
|
||||
|
||||
class CalendarSettingBloc
|
||||
extends Bloc<CalendarSettingEvent, CalendarSettingState> {
|
||||
final String viewId;
|
||||
final DatabaseController _databaseController;
|
||||
final DatabaseLayoutSettingListener _listener;
|
||||
|
||||
CalendarSettingBloc({
|
||||
required this.viewId,
|
||||
required CalendarLayoutSettingPB? layoutSettings,
|
||||
}) : _listener = DatabaseLayoutSettingListener(viewId),
|
||||
super(CalendarSettingState.initial(layoutSettings)) {
|
||||
required DatabaseController databaseController,
|
||||
}) : _databaseController = databaseController,
|
||||
_listener = DatabaseLayoutSettingListener(databaseController.viewId),
|
||||
super(
|
||||
CalendarSettingState.initial(
|
||||
databaseController.databaseLayoutSetting?.calendar,
|
||||
),
|
||||
) {
|
||||
on<CalendarSettingEvent>((event, emit) {
|
||||
event.when(
|
||||
init: () {
|
||||
initial: () {
|
||||
_startListening();
|
||||
},
|
||||
performAction: (action) {
|
||||
emit(state.copyWith(selectedAction: Some(action)));
|
||||
didUpdateLayoutSetting: (CalendarLayoutSettingPB setting) {
|
||||
emit(state.copyWith(layoutSetting: layoutSetting));
|
||||
},
|
||||
updateLayoutSetting: (setting) {
|
||||
emit(state.copyWith(layoutSetting: Some(setting)));
|
||||
updateLayoutSetting: (
|
||||
bool? showWeekends,
|
||||
bool? showWeekNumbers,
|
||||
int? firstDayOfWeek,
|
||||
String? layoutFieldId,
|
||||
) {
|
||||
_updateLayoutSettings(
|
||||
showWeekends,
|
||||
showWeekNumbers,
|
||||
firstDayOfWeek,
|
||||
layoutFieldId,
|
||||
emit,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _updateLayoutSettings(
|
||||
bool? showWeekends,
|
||||
bool? showWeekNumbers,
|
||||
int? firstDayOfWeek,
|
||||
String? layoutFieldId,
|
||||
Emitter<CalendarSettingState> emit,
|
||||
) {
|
||||
final currentSetting = state.layoutSetting;
|
||||
if (currentSetting == null) {
|
||||
return;
|
||||
}
|
||||
currentSetting.freeze();
|
||||
final newSetting = currentSetting.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;
|
||||
}
|
||||
});
|
||||
|
||||
_databaseController.updateLayoutSetting(
|
||||
calendarLayoutSetting: newSetting,
|
||||
);
|
||||
emit(state.copyWith(layoutSetting: newSetting));
|
||||
}
|
||||
|
||||
CalendarLayoutSettingPB? get layoutSetting =>
|
||||
_databaseController.databaseLayoutSetting?.calendar;
|
||||
|
||||
void _startListening() {
|
||||
_listener.start(
|
||||
onLayoutChanged: (result) {
|
||||
@ -42,8 +97,9 @@ class CalendarSettingBloc
|
||||
}
|
||||
|
||||
result.fold(
|
||||
(setting) =>
|
||||
add(CalendarSettingEvent.updateLayoutSetting(setting.calendar)),
|
||||
(setting) => add(
|
||||
CalendarSettingEvent.didUpdateLayoutSetting(setting.calendar),
|
||||
),
|
||||
(r) => Log.error(r),
|
||||
);
|
||||
},
|
||||
@ -57,34 +113,29 @@ class CalendarSettingBloc
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class CalendarSettingEvent with _$CalendarSettingEvent {
|
||||
const factory CalendarSettingEvent.init() = _Init;
|
||||
const factory CalendarSettingEvent.performAction(
|
||||
CalendarSettingAction action,
|
||||
) = _PerformAction;
|
||||
const factory CalendarSettingEvent.updateLayoutSetting(
|
||||
CalendarLayoutSettingPB setting,
|
||||
) = _UpdateLayoutSetting;
|
||||
}
|
||||
|
||||
enum CalendarSettingAction {
|
||||
properties,
|
||||
layout,
|
||||
}
|
||||
|
||||
@freezed
|
||||
class CalendarSettingState with _$CalendarSettingState {
|
||||
const factory CalendarSettingState({
|
||||
required Option<CalendarSettingAction> selectedAction,
|
||||
required Option<CalendarLayoutSettingPB> layoutSetting,
|
||||
required CalendarLayoutSettingPB? layoutSetting,
|
||||
}) = _CalendarSettingState;
|
||||
|
||||
factory CalendarSettingState.initial(
|
||||
CalendarLayoutSettingPB? layoutSettings,
|
||||
) =>
|
||||
CalendarSettingState(
|
||||
selectedAction: none(),
|
||||
layoutSetting: layoutSettings == null ? none() : Some(layoutSettings),
|
||||
);
|
||||
) {
|
||||
return CalendarSettingState(layoutSetting: layoutSettings);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class CalendarSettingEvent with _$CalendarSettingEvent {
|
||||
const factory CalendarSettingEvent.initial() = _Initial;
|
||||
const factory CalendarSettingEvent.didUpdateLayoutSetting(
|
||||
CalendarLayoutSettingPB setting,
|
||||
) = _DidUpdateLayoutSetting;
|
||||
const factory CalendarSettingEvent.updateLayoutSetting({
|
||||
bool? showWeekends,
|
||||
bool? showWeekNumbers,
|
||||
int? firstDayOfWeek,
|
||||
String? layoutFieldId,
|
||||
}) = _UpdateLayoutSetting;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_paginated_bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
@ -446,15 +446,21 @@ class _UnscheduledEventsButtonState extends State<UnscheduledEventsButton> {
|
||||
}
|
||||
|
||||
void _showUnscheduledEventsMobile(List<CalendarEventPB> events) =>
|
||||
showPaginatedBottomSheet(
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
page: SheetPage(
|
||||
title: LocaleKeys.calendar_settings_unscheduledEventsTitle.tr(),
|
||||
body: UnscheduleEventsList(
|
||||
builder: (_) {
|
||||
return Column(
|
||||
children: [
|
||||
FlowyText.medium(
|
||||
LocaleKeys.calendar_settings_unscheduledEventsTitle.tr(),
|
||||
),
|
||||
UnscheduleEventsList(
|
||||
databaseController: widget.databaseController,
|
||||
unscheduleEvents: events,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
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';
|
||||
import 'package:appflowy/plugins/database_view/application/database_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';
|
||||
@ -12,30 +12,15 @@ 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();
|
||||
|
||||
/// Updates the layout settings for the calendar view.
|
||||
void updateLayoutSettings(CalendarLayoutSettingPB layoutSettings);
|
||||
}
|
||||
|
||||
/// Widget that displays a list of settings that alters the appearance of the
|
||||
/// calendar
|
||||
class CalendarLayoutSetting extends StatefulWidget {
|
||||
final String viewId;
|
||||
final FieldController fieldController;
|
||||
final ICalendarSetting calendarSettingController;
|
||||
final DatabaseController databaseController;
|
||||
|
||||
const CalendarLayoutSetting({
|
||||
required this.viewId,
|
||||
required this.fieldController,
|
||||
required this.calendarSettingController,
|
||||
super.key,
|
||||
required this.databaseController,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -43,62 +28,65 @@ class CalendarLayoutSetting extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
|
||||
late final PopoverMutex popoverMutex = PopoverMutex();
|
||||
final PopoverMutex popoverMutex = PopoverMutex();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => CalendarSettingBloc(
|
||||
viewId: widget.viewId,
|
||||
layoutSettings: widget.calendarSettingController.getLayoutSetting(),
|
||||
)..add(const CalendarSettingEvent.init()),
|
||||
create: (context) {
|
||||
return CalendarSettingBloc(
|
||||
databaseController: widget.databaseController,
|
||||
)..add(const CalendarSettingEvent.initial());
|
||||
},
|
||||
child: BlocBuilder<CalendarSettingBloc, CalendarSettingState>(
|
||||
builder: (context, state) {
|
||||
final CalendarLayoutSettingPB? settings = state.layoutSetting
|
||||
.foldLeft(null, (previous, settings) => settings);
|
||||
final CalendarLayoutSettingPB? settings = state.layoutSetting;
|
||||
|
||||
if (settings == null) {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
|
||||
final availableSettings = _availableCalendarSettings(settings);
|
||||
final bloc = context.read<CalendarSettingBloc>();
|
||||
final items = availableSettings.map((setting) {
|
||||
switch (setting) {
|
||||
case CalendarLayoutSettingAction.showWeekNumber:
|
||||
return ShowWeekNumber(
|
||||
showWeekNumbers: settings.showWeekNumbers,
|
||||
onUpdated: (showWeekNumbers) => _updateLayoutSettings(
|
||||
context,
|
||||
onUpdated: (showWeekNumbers) => bloc.add(
|
||||
CalendarSettingEvent.updateLayoutSetting(
|
||||
showWeekNumbers: showWeekNumbers,
|
||||
),
|
||||
),
|
||||
);
|
||||
case CalendarLayoutSettingAction.showWeekends:
|
||||
return ShowWeekends(
|
||||
showWeekends: settings.showWeekends,
|
||||
onUpdated: (showWeekends) => _updateLayoutSettings(
|
||||
context,
|
||||
onUpdated: (showWeekends) => bloc.add(
|
||||
CalendarSettingEvent.updateLayoutSetting(
|
||||
showWeekends: showWeekends,
|
||||
),
|
||||
),
|
||||
);
|
||||
case CalendarLayoutSettingAction.firstDayOfWeek:
|
||||
return FirstDayOfWeek(
|
||||
firstDayOfWeek: settings.firstDayOfWeek,
|
||||
popoverMutex: popoverMutex,
|
||||
onUpdated: (firstDayOfWeek) => _updateLayoutSettings(
|
||||
context,
|
||||
onUpdated: (firstDayOfWeek) => bloc.add(
|
||||
CalendarSettingEvent.updateLayoutSetting(
|
||||
firstDayOfWeek: firstDayOfWeek,
|
||||
),
|
||||
),
|
||||
);
|
||||
case CalendarLayoutSettingAction.layoutField:
|
||||
return LayoutDateField(
|
||||
fieldController: widget.fieldController,
|
||||
viewId: widget.viewId,
|
||||
databaseController: widget.databaseController,
|
||||
fieldId: settings.fieldId,
|
||||
popoverMutex: popoverMutex,
|
||||
onUpdated: (fieldId) => _updateLayoutSettings(
|
||||
context,
|
||||
onUpdated: (fieldId) => bloc.add(
|
||||
CalendarSettingEvent.updateLayoutSetting(
|
||||
layoutFieldId: fieldId,
|
||||
),
|
||||
),
|
||||
);
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
@ -140,59 +128,19 @@ class _CalendarLayoutSettingState extends State<CalendarLayoutSetting> {
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
void _updateLayoutSettings(
|
||||
BuildContext context, {
|
||||
bool? showWeekends,
|
||||
bool? showWeekNumbers,
|
||||
int? firstDayOfWeek,
|
||||
String? layoutFieldId,
|
||||
}) {
|
||||
CalendarLayoutSettingPB setting = context
|
||||
.read<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;
|
||||
}
|
||||
});
|
||||
|
||||
context
|
||||
.read<CalendarSettingBloc>()
|
||||
.add(CalendarSettingEvent.updateLayoutSetting(setting));
|
||||
|
||||
widget.calendarSettingController.updateLayoutSettings(setting);
|
||||
}
|
||||
}
|
||||
|
||||
class LayoutDateField extends StatelessWidget {
|
||||
const LayoutDateField({
|
||||
super.key,
|
||||
required this.databaseController,
|
||||
required this.fieldId,
|
||||
required this.fieldController,
|
||||
required this.viewId,
|
||||
required this.popoverMutex,
|
||||
required this.onUpdated,
|
||||
});
|
||||
|
||||
final DatabaseController databaseController;
|
||||
final String fieldId;
|
||||
final String viewId;
|
||||
final FieldController fieldController;
|
||||
final PopoverMutex popoverMutex;
|
||||
final Function(String fieldId) onUpdated;
|
||||
|
||||
@ -207,8 +155,8 @@ class LayoutDateField extends StatelessWidget {
|
||||
popupBuilder: (context) {
|
||||
return BlocProvider(
|
||||
create: (context) => DatabasePropertyBloc(
|
||||
viewId: viewId,
|
||||
fieldController: fieldController,
|
||||
viewId: databaseController.viewId,
|
||||
fieldController: databaseController.fieldController,
|
||||
)..add(const DatabasePropertyEvent.initial()),
|
||||
child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>(
|
||||
builder: (context, state) {
|
||||
|
@ -123,7 +123,7 @@ class _DatabaseTabBarState extends State<DatabaseTabBar> {
|
||||
AddDatabaseViewButton(
|
||||
onTap: (layoutType) async {
|
||||
context.read<DatabaseTabBarBloc>().add(
|
||||
DatabaseTabBarEvent.createView(layoutType),
|
||||
DatabaseTabBarEvent.createView(layoutType, null),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -1,9 +1,7 @@
|
||||
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_backend/protobuf/flowy-database2/setting_entities.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -73,36 +71,6 @@ class DatabaseViewLayoutCell extends StatelessWidget {
|
||||
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(
|
||||
@ -126,31 +94,3 @@ class DesktopDatabaseViewLayoutCell extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,11 @@
|
||||
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_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
@ -72,37 +66,17 @@ extension DatabaseSettingActionExtension on DatabaseSettingAction {
|
||||
fieldController: databaseController.fieldController,
|
||||
),
|
||||
DatabaseSettingAction.showCalendarLayout => CalendarLayoutSetting(
|
||||
viewId: databaseController.viewId,
|
||||
fieldController: databaseController.fieldController,
|
||||
calendarSettingController: ICalendarSettingImpl(
|
||||
databaseController,
|
||||
),
|
||||
databaseController: databaseController,
|
||||
),
|
||||
};
|
||||
|
||||
return AppFlowyPopover(
|
||||
triggerActions: PlatformExtension.isMobile
|
||||
? PopoverTriggerFlags.none
|
||||
: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
||||
triggerActions: 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(
|
||||
child: SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
onTap: null,
|
||||
@ -120,82 +94,4 @@ extension DatabaseSettingActionExtension on DatabaseSettingAction {
|
||||
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,
|
||||
),
|
||||
Icon(
|
||||
Icons.chevron_right,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
],
|
||||
),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
@ -1,347 +0,0 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
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_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/calendar/application/calendar_setting_bloc.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_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' 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,
|
||||
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);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,15 +1,13 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/view/database_view_list.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/view/edit_database_view_screen.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_paginated_bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/tab_bar_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/filter/filter_menu_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/sort/sort_menu_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/setting/database_settings_list.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@ -66,16 +64,48 @@ class MobileDatabaseControls extends StatelessWidget {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
padding: EdgeInsets.zero,
|
||||
builder: (_) => MobileEditDatabaseViewScreen(
|
||||
builder: (_) {
|
||||
return BlocProvider<ViewBloc>(
|
||||
create: (_) {
|
||||
return ViewBloc(
|
||||
view: context
|
||||
.read<DatabaseTabBarBloc>()
|
||||
.state
|
||||
.tabBarControllerByViewId[controller.viewId]!
|
||||
.view,
|
||||
)..add(const ViewEvent.initial());
|
||||
},
|
||||
child: MobileEditDatabaseViewScreen(
|
||||
databaseController: controller,
|
||||
viewPB: context.read<ViewBloc>().state.view,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
_DatabaseControlButton(
|
||||
icon: FlowySvgs.align_left_s,
|
||||
onTap: () => _showMobileSettings(context, controller),
|
||||
onTap: () {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
padding: EdgeInsets.zero,
|
||||
builder: (_) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<ViewBloc>.value(
|
||||
value: context.read<ViewBloc>(),
|
||||
),
|
||||
BlocProvider<DatabaseTabBarBloc>.value(
|
||||
value: context.read<DatabaseTabBarBloc>(),
|
||||
),
|
||||
],
|
||||
child: MobileDatabaseViewList(
|
||||
databaseController: controller,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
@ -84,20 +114,6 @@ class MobileDatabaseControls extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showMobileSettings(
|
||||
BuildContext context,
|
||||
DatabaseController controller,
|
||||
) =>
|
||||
showPaginatedBottomSheet(
|
||||
context,
|
||||
page: SheetPage(
|
||||
title: LocaleKeys.settings_title.tr(),
|
||||
body: DatabaseSettingsList(
|
||||
databaseController: controller,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _DatabaseControlButton extends StatelessWidget {
|
||||
@ -111,9 +127,8 @@ class _DatabaseControlButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 36,
|
||||
width: 36,
|
||||
return SizedBox.square(
|
||||
dimension: 36,
|
||||
child: IconButton(
|
||||
splashRadius: 18,
|
||||
padding: EdgeInsets.zero,
|
||||
|
@ -2,10 +2,8 @@ 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/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_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
@ -52,19 +50,3 @@ class _SettingButtonState extends State<SettingButton> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ICalendarSettingImpl extends ICalendarSetting {
|
||||
const ICalendarSettingImpl(this._databaseController);
|
||||
|
||||
final DatabaseController _databaseController;
|
||||
|
||||
@override
|
||||
void updateLayoutSettings(CalendarLayoutSettingPB layoutSettings) =>
|
||||
_databaseController.updateLayoutSetting(
|
||||
calendarLayoutSetting: layoutSettings,
|
||||
);
|
||||
|
||||
@override
|
||||
CalendarLayoutSettingPB? getLayoutSetting() =>
|
||||
_databaseController.databaseLayoutSetting?.calendar;
|
||||
}
|
||||
|
@ -465,6 +465,7 @@
|
||||
"typeAValue": "Type a value...",
|
||||
"layout": "Layout",
|
||||
"databaseLayout": "Layout",
|
||||
"viewList": "Database Views",
|
||||
"editView": "Edit View",
|
||||
"boardSettings": "Board settings",
|
||||
"calendarSettings": "Calendar settings",
|
||||
@ -911,6 +912,7 @@
|
||||
"showWeekends": "Show weekends",
|
||||
"firstDayOfWeek": "Start week on",
|
||||
"layoutDateField": "Layout calendar by",
|
||||
"changeLayoutDateField": "Change layout field",
|
||||
"noDateTitle": "No Date",
|
||||
"noDateHint": {
|
||||
"zero": "Unscheduled events will show up here",
|
||||
|
Loading…
Reference in New Issue
Block a user