mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: revamp database setting action sheets (#4570)
* chore: revamp database setting action sheets * chore: field picker improvement * chore: fix comments
This commit is contained in:
parent
9d28360887
commit
20391bfec4
@ -85,6 +85,39 @@ class AppBarDoneButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class AppBarFilledDoneButton extends StatelessWidget {
|
||||
const AppBarFilledDoneButton({super.key, required this.onTap});
|
||||
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return 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: onTap,
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.button_done.tr(),
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppBarMoreButton extends StatelessWidget {
|
||||
const AppBarMoreButton({
|
||||
super.key,
|
||||
|
@ -4,7 +4,6 @@ export 'bottom_sheet_drag_handler.dart';
|
||||
export 'bottom_sheet_rename_widget.dart';
|
||||
export 'bottom_sheet_view_item.dart';
|
||||
export 'bottom_sheet_view_item_body.dart';
|
||||
export 'bottom_sheet_view_item_header.dart';
|
||||
export 'bottom_sheet_view_page.dart';
|
||||
export 'default_mobile_action_pane.dart';
|
||||
export 'show_mobile_bottom_sheet.dart';
|
||||
|
@ -1,59 +0,0 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class MobileViewItemBottomSheetHeader extends StatelessWidget {
|
||||
const MobileViewItemBottomSheetHeader({
|
||||
super.key,
|
||||
required this.view,
|
||||
required this.showBackButton,
|
||||
required this.onBack,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
final bool showBackButton;
|
||||
final VoidCallback onBack;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// back button,
|
||||
showBackButton
|
||||
? InkWell(
|
||||
onTap: onBack,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Icon(
|
||||
Icons.arrow_back_ios_new_rounded,
|
||||
size: 24.0,
|
||||
),
|
||||
),
|
||||
)
|
||||
: FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text: const Icon(
|
||||
Icons.close,
|
||||
),
|
||||
margin: EdgeInsets.zero,
|
||||
onTap: () {
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
// title
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.6,
|
||||
),
|
||||
child: FlowyText.medium(
|
||||
view.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const HSpace(24.0),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -7,10 +7,12 @@ import 'package:flutter/material.dart';
|
||||
Future<T?> showMobileBottomSheet<T>(
|
||||
BuildContext context, {
|
||||
required WidgetBuilder builder,
|
||||
bool useSafeArea = true,
|
||||
bool isDragEnabled = true,
|
||||
bool showDragHandle = false,
|
||||
bool showHeader = false,
|
||||
// this field is only used if showHeader is true
|
||||
bool showBackButton = false,
|
||||
bool showCloseButton = false,
|
||||
// this field is only used if showHeader is true
|
||||
String title = '',
|
||||
@ -20,7 +22,7 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
bool useRootNavigator = false,
|
||||
ShapeBorder? shape,
|
||||
// the padding of the content, the padding of the header area is fixed
|
||||
EdgeInsets padding = const EdgeInsets.all(0.0),
|
||||
EdgeInsets padding = EdgeInsets.zero,
|
||||
Color? backgroundColor,
|
||||
BoxConstraints? constraints,
|
||||
Color? barrierColor,
|
||||
@ -32,10 +34,11 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
double maxChildSize = 0.8,
|
||||
double initialChildSize = 0.51,
|
||||
}) async {
|
||||
assert(() {
|
||||
if (showCloseButton || title.isNotEmpty) assert(showHeader);
|
||||
return true;
|
||||
}());
|
||||
assert(
|
||||
showHeader ||
|
||||
title.isEmpty && !showCloseButton && !showBackButton && !showDoneButton,
|
||||
);
|
||||
assert(!(showCloseButton && showBackButton));
|
||||
|
||||
shape ??= const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
@ -83,6 +86,7 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
children.add(
|
||||
_Header(
|
||||
showCloseButton: showCloseButton,
|
||||
showBackButton: showBackButton,
|
||||
showDoneButton: showDoneButton,
|
||||
title: title,
|
||||
),
|
||||
@ -147,23 +151,30 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
VSpace(MediaQuery.of(context).padding.bottom == 0 ? 28.0 : 16.0),
|
||||
);
|
||||
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
return useSafeArea
|
||||
? SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class _Header extends StatelessWidget {
|
||||
const _Header({
|
||||
required this.showBackButton,
|
||||
required this.showCloseButton,
|
||||
required this.title,
|
||||
required this.showDoneButton,
|
||||
});
|
||||
|
||||
final bool showBackButton;
|
||||
final bool showCloseButton;
|
||||
final String title;
|
||||
final bool showDoneButton;
|
||||
@ -176,6 +187,11 @@ class _Header extends StatelessWidget {
|
||||
height: 44.0, // the height of the header area is fixed
|
||||
child: Stack(
|
||||
children: [
|
||||
if (showBackButton)
|
||||
const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: AppBarBackButton(),
|
||||
),
|
||||
if (showCloseButton)
|
||||
const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
|
@ -128,14 +128,17 @@ void showQuickEditField(
|
||||
/// Display a list of fields in the current database that users can choose from.
|
||||
Future<String?> showFieldPicker(
|
||||
BuildContext context,
|
||||
String title,
|
||||
String? selectedFieldId,
|
||||
FieldController fieldController,
|
||||
bool Function(FieldInfo fieldInfo) filterBy,
|
||||
) {
|
||||
return showMobileBottomSheet<String>(
|
||||
context,
|
||||
showDivider: false,
|
||||
builder: (context) {
|
||||
return MobileFieldPickerList(
|
||||
title: title,
|
||||
selectedFieldId: selectedFieldId,
|
||||
fieldController: fieldController,
|
||||
filterBy: filterBy,
|
||||
|
@ -1,11 +1,10 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
|
||||
import 'package:appflowy/plugins/base/drag_handler.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_info.dart';
|
||||
import 'package:appflowy/util/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';
|
||||
@ -13,11 +12,13 @@ import 'package:go_router/go_router.dart';
|
||||
class MobileFieldPickerList extends StatefulWidget {
|
||||
MobileFieldPickerList({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.selectedFieldId,
|
||||
required FieldController fieldController,
|
||||
required bool Function(FieldInfo fieldInfo) filterBy,
|
||||
}) : fields = fieldController.fieldInfos.where(filterBy).toList();
|
||||
|
||||
final String title;
|
||||
final String? selectedFieldId;
|
||||
final List<FieldInfo> fields;
|
||||
|
||||
@ -36,87 +37,79 @@ class _MobileFieldPickerListState extends State<MobileFieldPickerList> {
|
||||
|
||||
@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,
|
||||
return DraggableScrollableSheet(
|
||||
expand: false,
|
||||
snap: true,
|
||||
initialChildSize: 0.98,
|
||||
minChildSize: 0.98,
|
||||
maxChildSize: 0.98,
|
||||
builder: (context, scrollController) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
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),
|
||||
),
|
||||
),
|
||||
const DragHandler(),
|
||||
_Header(
|
||||
title: widget.title,
|
||||
onDone: (context) => context.pop(newFieldId),
|
||||
),
|
||||
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_done.tr(),
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Same header as the one in showMobileBottomSheet, but allows popping the
|
||||
/// sheet with a value.
|
||||
class _Header extends StatelessWidget {
|
||||
const _Header({
|
||||
required this.title,
|
||||
required this.onDone,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final void Function(BuildContext context) onDone;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: SizedBox(
|
||||
height: 44.0,
|
||||
child: Stack(
|
||||
children: [
|
||||
const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: AppBarBackButton(),
|
||||
),
|
||||
Align(
|
||||
child: FlowyText.medium(
|
||||
title,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: AppBarDoneButton(
|
||||
onTap: () => onDone(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Center(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.calendar_settings_changeLayoutDateField.tr(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import 'dart:ui';
|
||||
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/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_info.dart';
|
||||
@ -11,7 +10,6 @@ import 'package:appflowy/plugins/database/application/setting/property_bloc.dart
|
||||
import 'package:appflowy/plugins/database/widgets/setting/field_visibility_extension.dart';
|
||||
import 'package:appflowy/util/field_type_extension.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@ -30,61 +28,9 @@ class MobileDatabaseFieldList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DraggableScrollableSheet(
|
||||
expand: false,
|
||||
snap: true,
|
||||
initialChildSize: 1.0,
|
||||
minChildSize: 0.0,
|
||||
builder: (context, controller) {
|
||||
return Material(
|
||||
child: Column(
|
||||
children: [
|
||||
const Center(child: DragHandler()),
|
||||
const _MobileDatabaseFieldListHeader(),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: _MobileDatabaseFieldListBody(
|
||||
databaseController: databaseController,
|
||||
view: context.read<ViewBloc>().state.view,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MobileDatabaseFieldListHeader extends StatelessWidget {
|
||||
const _MobileDatabaseFieldListHeader();
|
||||
|
||||
@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.arrow_left_m,
|
||||
size: Size.square(iconWidth),
|
||||
),
|
||||
onPressed: () => Navigator.of(context).maybePop(),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.grid_settings_properties.tr(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
return _MobileDatabaseFieldListBody(
|
||||
databaseController: databaseController,
|
||||
viewId: context.read<ViewBloc>().state.view.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -92,17 +38,17 @@ class _MobileDatabaseFieldListHeader extends StatelessWidget {
|
||||
class _MobileDatabaseFieldListBody extends StatelessWidget {
|
||||
const _MobileDatabaseFieldListBody({
|
||||
required this.databaseController,
|
||||
required this.view,
|
||||
required this.viewId,
|
||||
});
|
||||
|
||||
final DatabaseController databaseController;
|
||||
final ViewPB view;
|
||||
final String viewId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<DatabasePropertyBloc>(
|
||||
create: (_) => DatabasePropertyBloc(
|
||||
viewId: view.id,
|
||||
viewId: viewId,
|
||||
fieldController: databaseController.fieldController,
|
||||
)..add(const DatabasePropertyEvent.initial()),
|
||||
child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>(
|
||||
@ -114,7 +60,7 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
|
||||
final firstField = fields.removeAt(0);
|
||||
final firstCell = DatabaseFieldListTile(
|
||||
key: ValueKey(firstField.id),
|
||||
viewId: view.id,
|
||||
viewId: viewId,
|
||||
fieldController: databaseController.fieldController,
|
||||
fieldInfo: firstField,
|
||||
showTopBorder: true,
|
||||
@ -123,7 +69,7 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
|
||||
.mapIndexed(
|
||||
(index, field) => DatabaseFieldListTile(
|
||||
key: ValueKey(field.id),
|
||||
viewId: view.id,
|
||||
viewId: viewId,
|
||||
fieldController: databaseController.fieldController,
|
||||
fieldInfo: field,
|
||||
index: index,
|
||||
@ -133,6 +79,7 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
|
||||
.toList();
|
||||
|
||||
return ReorderableListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
proxyDecorator: (_, index, anim) {
|
||||
final field = fields[index];
|
||||
return AnimatedBuilder(
|
||||
@ -146,7 +93,7 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
|
||||
child: Material(
|
||||
child: DatabaseFieldListTile(
|
||||
key: ValueKey(field.id),
|
||||
viewId: view.id,
|
||||
viewId: viewId,
|
||||
fieldController: databaseController.fieldController,
|
||||
fieldInfo: field,
|
||||
index: index,
|
||||
@ -170,7 +117,7 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_divider(),
|
||||
_NewDatabaseFieldTile(viewId: view.id),
|
||||
_NewDatabaseFieldTile(viewId: viewId),
|
||||
],
|
||||
),
|
||||
itemCount: cells.length,
|
||||
|
@ -168,6 +168,7 @@ class _CalendarLayoutField extends StatelessWidget {
|
||||
onTap: () async {
|
||||
final newFieldId = await showFieldPicker(
|
||||
context,
|
||||
LocaleKeys.calendar_settings_changeLayoutDateField.tr(),
|
||||
selectedFieldId,
|
||||
databaseController.fieldController,
|
||||
(field) => field.fieldType == FieldType.DateTime,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/base/drag_handler.dart';
|
||||
@ -26,28 +27,110 @@ class MobileDatabaseViewList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ViewBloc, ViewState>(
|
||||
builder: (context, state) {
|
||||
final views = [state.view, ...state.view.childViews];
|
||||
final children = [
|
||||
...views.mapIndexed(
|
||||
(index, view) => MobileDatabaseViewListButton(
|
||||
view: view,
|
||||
showTopBorder: index == 0,
|
||||
),
|
||||
),
|
||||
const VSpace(20),
|
||||
const MobileNewDatabaseViewButton(),
|
||||
];
|
||||
return DraggableScrollableSheet(
|
||||
expand: false,
|
||||
snap: true,
|
||||
initialChildSize: 0.98,
|
||||
minChildSize: 0.98,
|
||||
maxChildSize: 0.98,
|
||||
builder: (context, scrollController) {
|
||||
return BlocBuilder<ViewBloc, ViewState>(
|
||||
builder: (context, state) {
|
||||
final views = [state.view, ...state.view.childViews];
|
||||
|
||||
return Column(
|
||||
children: children,
|
||||
return Column(
|
||||
children: [
|
||||
const DragHandler(),
|
||||
_Header(
|
||||
title: LocaleKeys.grid_settings_viewList.plural(
|
||||
context.watch<DatabaseTabBarBloc>().state.tabBars.length,
|
||||
namedArgs: {
|
||||
'count':
|
||||
'${context.watch<DatabaseTabBarBloc>().state.tabBars.length}',
|
||||
},
|
||||
),
|
||||
showBackButton: false,
|
||||
useFilledDoneButton: false,
|
||||
onDone: (context) => Navigator.pop(context),
|
||||
),
|
||||
SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: Column(
|
||||
children: [
|
||||
...views.mapIndexed(
|
||||
(index, view) => MobileDatabaseViewListButton(
|
||||
view: view,
|
||||
showTopBorder: index == 0,
|
||||
),
|
||||
),
|
||||
const VSpace(20),
|
||||
const MobileNewDatabaseViewButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Same header as the one in showMobileBottomSheet, but allows popping the
|
||||
/// sheet with a value.
|
||||
class _Header extends StatelessWidget {
|
||||
const _Header({
|
||||
required this.title,
|
||||
required this.showBackButton,
|
||||
required this.useFilledDoneButton,
|
||||
required this.onDone,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final bool showBackButton;
|
||||
final bool useFilledDoneButton;
|
||||
final void Function(BuildContext context) onDone;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: SizedBox(
|
||||
height: 44.0,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (showBackButton)
|
||||
const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: AppBarBackButton(),
|
||||
),
|
||||
Align(
|
||||
child: FlowyText.medium(
|
||||
title,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
useFilledDoneButton
|
||||
? Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: AppBarFilledDoneButton(
|
||||
onTap: () => onDone(context),
|
||||
),
|
||||
)
|
||||
: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: AppBarDoneButton(
|
||||
onTap: () => onDone(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
class MobileDatabaseViewListButton extends StatelessWidget {
|
||||
const MobileDatabaseViewListButton({
|
||||
@ -155,6 +238,7 @@ class MobileNewDatabaseViewButton extends StatelessWidget {
|
||||
onTap: () async {
|
||||
final result = await showMobileBottomSheet<(DatabaseLayoutPB, String)>(
|
||||
context,
|
||||
showDragHandle: true,
|
||||
builder: (_) {
|
||||
return const MobileCreateDatabaseView();
|
||||
},
|
||||
@ -199,12 +283,13 @@ class _MobileCreateDatabaseViewState extends State<MobileCreateDatabaseView> {
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const Center(child: DragHandler()),
|
||||
_CreateViewHeader(
|
||||
textController: controller,
|
||||
selectedLayout: layoutType,
|
||||
_Header(
|
||||
title: LocaleKeys.grid_settings_createView.tr(),
|
||||
showBackButton: true,
|
||||
useFilledDoneButton: true,
|
||||
onDone: (context) =>
|
||||
context.pop((layoutType, controller.text.trim())),
|
||||
),
|
||||
const VSpace(4.0),
|
||||
FlowyOptionTile.textField(
|
||||
autofocus: true,
|
||||
controller: controller,
|
||||
@ -220,74 +305,3 @@ class _MobileCreateDatabaseViewState extends State<MobileCreateDatabaseView> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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(24),
|
||||
),
|
||||
),
|
||||
),
|
||||
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,5 +1,6 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
@ -13,7 +14,7 @@ import 'edit_database_view_screen.dart';
|
||||
|
||||
/// [MobileDatabaseViewQuickActions] is gives users to quickly edit a database
|
||||
/// view from the [MobileDatabaseViewList]
|
||||
class MobileDatabaseViewQuickActions extends StatefulWidget {
|
||||
class MobileDatabaseViewQuickActions extends StatelessWidget {
|
||||
const MobileDatabaseViewQuickActions({
|
||||
super.key,
|
||||
required this.view,
|
||||
@ -23,49 +24,46 @@ class MobileDatabaseViewQuickActions extends StatefulWidget {
|
||||
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 isEditing
|
||||
? MobileEditDatabaseViewScreen(
|
||||
databaseController: widget.databaseController,
|
||||
)
|
||||
: _quickActions(context, widget.view);
|
||||
}
|
||||
|
||||
Widget _quickActions(BuildContext context, ViewPB view) {
|
||||
final isInline = view.childViews.isNotEmpty;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_actionButton(context, _Action.edit, () {
|
||||
setState(() => isEditing = true);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_actionButton(context, _Action.edit, () {
|
||||
final bloc = context.read<ViewBloc>();
|
||||
context.pop();
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
showDoneButton: true,
|
||||
title: LocaleKeys.grid_settings_editView.tr(),
|
||||
enableDraggableScrollable: true,
|
||||
initialChildSize: 0.98,
|
||||
minChildSize: 0.98,
|
||||
maxChildSize: 0.98,
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: bloc,
|
||||
child: MobileEditDatabaseViewScreen(
|
||||
databaseController: databaseController,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
if (!isInline) ...[
|
||||
_divider(),
|
||||
_actionButton(context, _Action.duplicate, () {
|
||||
context.read<ViewBloc>().add(const ViewEvent.duplicate());
|
||||
context.pop();
|
||||
}),
|
||||
if (!isInline) ...[
|
||||
_divider(),
|
||||
_actionButton(context, _Action.duplicate, () {
|
||||
context.read<ViewBloc>().add(const ViewEvent.duplicate());
|
||||
context.pop();
|
||||
}),
|
||||
_divider(),
|
||||
_actionButton(context, _Action.delete, () {
|
||||
context.read<ViewBloc>().add(const ViewEvent.delete());
|
||||
context.pop();
|
||||
}),
|
||||
_divider(),
|
||||
],
|
||||
_divider(),
|
||||
_actionButton(context, _Action.delete, () {
|
||||
context.read<ViewBloc>().add(const ViewEvent.delete());
|
||||
context.pop();
|
||||
}),
|
||||
_divider(),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,35 +0,0 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'edit_database_view_cubit.freezed.dart';
|
||||
|
||||
class MobileEditDatabaseViewCubit extends Cubit<MobileDatabaseViewEditorState> {
|
||||
MobileEditDatabaseViewCubit()
|
||||
: super(
|
||||
MobileDatabaseViewEditorState.initial(),
|
||||
);
|
||||
|
||||
void changePage(MobileEditDatabaseViewPageEnum newPage) {
|
||||
emit(MobileDatabaseViewEditorState(currentPage: newPage));
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class MobileDatabaseViewEditorState with _$MobileDatabaseViewEditorState {
|
||||
factory MobileDatabaseViewEditorState({
|
||||
required MobileEditDatabaseViewPageEnum currentPage,
|
||||
}) = _MobileDatabaseViewEditorState;
|
||||
|
||||
factory MobileDatabaseViewEditorState.initial() =>
|
||||
MobileDatabaseViewEditorState(
|
||||
currentPage: MobileEditDatabaseViewPageEnum.main,
|
||||
);
|
||||
}
|
||||
|
||||
enum MobileEditDatabaseViewPageEnum {
|
||||
main,
|
||||
fields,
|
||||
filter,
|
||||
sort,
|
||||
}
|
@ -2,7 +2,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
|
||||
import 'package:appflowy/plugins/base/drag_handler.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_view_service.dart';
|
||||
import 'package:appflowy/plugins/database/application/layout/layout_service.dart';
|
||||
@ -18,12 +17,11 @@ import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'database_field_list.dart';
|
||||
import 'database_view_layout.dart';
|
||||
import 'edit_database_view_cubit.dart';
|
||||
|
||||
/// [MobileEditDatabaseViewScreen] is the main widget used to edit a database
|
||||
/// view. It contains multiple sub-pages, and the current page is managed by
|
||||
/// [MobileEditDatabaseViewCubit]
|
||||
class MobileEditDatabaseViewScreen extends StatefulWidget {
|
||||
class MobileEditDatabaseViewScreen extends StatelessWidget {
|
||||
const MobileEditDatabaseViewScreen({
|
||||
super.key,
|
||||
required this.databaseController,
|
||||
@ -31,130 +29,12 @@ class MobileEditDatabaseViewScreen extends StatefulWidget {
|
||||
|
||||
final DatabaseController databaseController;
|
||||
|
||||
@override
|
||||
State<MobileEditDatabaseViewScreen> createState() =>
|
||||
_MobileEditDatabaseViewScreenState();
|
||||
}
|
||||
|
||||
class _MobileEditDatabaseViewScreenState
|
||||
extends State<MobileEditDatabaseViewScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<MobileEditDatabaseViewCubit>(
|
||||
create: (context) => MobileEditDatabaseViewCubit(),
|
||||
child: BlocBuilder<MobileEditDatabaseViewCubit,
|
||||
MobileDatabaseViewEditorState>(
|
||||
builder: (context, state) {
|
||||
return switch (state.currentPage) {
|
||||
MobileEditDatabaseViewPageEnum.main => _EditDatabaseViewMainPage(
|
||||
databaseController: widget.databaseController,
|
||||
),
|
||||
MobileEditDatabaseViewPageEnum.fields => _wrapSubPage(
|
||||
context,
|
||||
MobileDatabaseFieldList(
|
||||
databaseController: widget.databaseController,
|
||||
),
|
||||
),
|
||||
_ => const SizedBox.shrink(),
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _wrapSubPage(BuildContext context, Widget child) {
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: child,
|
||||
onPopInvoked: (_) {
|
||||
context
|
||||
.read<MobileEditDatabaseViewCubit>()
|
||||
.changePage(MobileEditDatabaseViewPageEnum.main);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EditDatabaseViewMainPage extends StatelessWidget {
|
||||
const _EditDatabaseViewMainPage({
|
||||
required this.databaseController,
|
||||
});
|
||||
|
||||
final DatabaseController databaseController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DraggableScrollableSheet(
|
||||
expand: false,
|
||||
snap: true,
|
||||
initialChildSize: 1.0,
|
||||
minChildSize: 0.0,
|
||||
builder: (context, controller) {
|
||||
return Material(
|
||||
child: Column(
|
||||
children: [
|
||||
const Center(child: DragHandler()),
|
||||
const _EditDatabaseViewHeader(),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: _EditDatabaseViewBody(
|
||||
databaseController: databaseController,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EditDatabaseViewHeader extends StatelessWidget {
|
||||
const _EditDatabaseViewHeader();
|
||||
|
||||
@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),
|
||||
),
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.grid_settings_editView.tr(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EditDatabaseViewBody extends StatelessWidget {
|
||||
const _EditDatabaseViewBody({
|
||||
required this.databaseController,
|
||||
});
|
||||
|
||||
final DatabaseController databaseController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ViewBloc, ViewState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
_NameAndIcon(view: state.view),
|
||||
_divider(),
|
||||
@ -207,6 +87,7 @@ class _NameAndIconState extends State<_NameAndIcon> {
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyOptionTile.textField(
|
||||
autofocus: true,
|
||||
showTopBorder: false,
|
||||
controller: textEditingController,
|
||||
onTextChanged: (text) {
|
||||
context.read<ViewBloc>().add(ViewEvent.rename(text));
|
||||
@ -250,15 +131,6 @@ enum DatabaseViewSettings {
|
||||
delete => FlowySvgs.delete_s,
|
||||
};
|
||||
}
|
||||
|
||||
MobileEditDatabaseViewPageEnum? get subPage {
|
||||
return switch (this) {
|
||||
fields => MobileEditDatabaseViewPageEnum.fields,
|
||||
filter => MobileEditDatabaseViewPageEnum.filter,
|
||||
sort => MobileEditDatabaseViewPageEnum.sort,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class DatabaseViewSettingTile extends StatelessWidget {
|
||||
@ -325,25 +197,18 @@ class DatabaseViewSettingTile extends StatelessWidget {
|
||||
}
|
||||
|
||||
void _onTap(BuildContext context) async {
|
||||
final subPage = setting.subPage;
|
||||
|
||||
if (subPage != null) {
|
||||
context.read<MobileEditDatabaseViewCubit>().changePage(subPage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (setting == DatabaseViewSettings.layout) {
|
||||
final databaseLayout = databaseLayoutFromViewLayout(view.layout);
|
||||
final newLayout = await showMobileBottomSheet<DatabaseLayoutPB>(
|
||||
context,
|
||||
resizeToAvoidBottomInset: false,
|
||||
showDragHandle: true,
|
||||
showHeader: true,
|
||||
showDivider: false,
|
||||
title: LocaleKeys.grid_settings_layout.tr(),
|
||||
builder: (context) {
|
||||
return DatabaseViewLayoutPicker(
|
||||
selectedLayout: databaseLayout,
|
||||
onSelect: (layout) {
|
||||
Navigator.of(context).pop(layout);
|
||||
},
|
||||
onSelect: (layout) => Navigator.of(context).pop(layout),
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -356,6 +221,32 @@ class DatabaseViewSettingTile extends StatelessWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
if (setting == DatabaseViewSettings.fields) {
|
||||
await showMobileBottomSheet(
|
||||
context,
|
||||
useSafeArea: false,
|
||||
resizeToAvoidBottomInset: false,
|
||||
showDragHandle: true,
|
||||
showHeader: true,
|
||||
showBackButton: true,
|
||||
title: LocaleKeys.grid_settings_properties.tr(),
|
||||
showDivider: true,
|
||||
enableDraggableScrollable: true,
|
||||
initialChildSize: 0.98,
|
||||
minChildSize: 0.98,
|
||||
maxChildSize: 0.98,
|
||||
builder: (_) {
|
||||
return BlocProvider.value(
|
||||
value: context.read<ViewBloc>(),
|
||||
child: MobileDatabaseFieldList(
|
||||
databaseController: databaseController,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (setting == DatabaseViewSettings.board) {
|
||||
await showMobileBottomSheet<DatabaseLayoutPB>(
|
||||
context,
|
||||
@ -375,13 +266,13 @@ class DatabaseViewSettingTile extends StatelessWidget {
|
||||
if (setting == DatabaseViewSettings.calendar) {
|
||||
await showMobileBottomSheet<DatabaseLayoutPB>(
|
||||
context,
|
||||
resizeToAvoidBottomInset: false,
|
||||
showDragHandle: true,
|
||||
showHeader: true,
|
||||
showDivider: false,
|
||||
title: LocaleKeys.calendar_settings_name.tr(),
|
||||
builder: (context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 24, bottom: 46),
|
||||
child: MobileCalendarViewLayoutSettings(
|
||||
databaseController: databaseController,
|
||||
),
|
||||
return MobileCalendarViewLayoutSettings(
|
||||
databaseController: databaseController,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -65,7 +65,13 @@ class MobileDatabaseControls extends StatelessWidget {
|
||||
onTap: () {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
padding: EdgeInsets.zero,
|
||||
showHeader: true,
|
||||
showDoneButton: true,
|
||||
title: LocaleKeys.grid_settings_editView.tr(),
|
||||
enableDraggableScrollable: true,
|
||||
initialChildSize: 0.98,
|
||||
minChildSize: 0.98,
|
||||
maxChildSize: 0.98,
|
||||
builder: (_) {
|
||||
return BlocProvider<ViewBloc>(
|
||||
create: (_) {
|
||||
@ -90,11 +96,7 @@ class MobileDatabaseControls extends StatelessWidget {
|
||||
onTap: () {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
showCloseButton: true,
|
||||
showDragHandle: true,
|
||||
showDivider: false,
|
||||
title: LocaleKeys.grid_settings_viewList.tr(),
|
||||
builder: (_) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
|
@ -480,7 +480,11 @@
|
||||
"typeAValue": "Type a value...",
|
||||
"layout": "Layout",
|
||||
"databaseLayout": "Layout",
|
||||
"viewList": "Database Views",
|
||||
"viewList": {
|
||||
"zero": "0 views",
|
||||
"one": "{count} view",
|
||||
"other": "{count} views"
|
||||
},
|
||||
"editView": "Edit View",
|
||||
"boardSettings": "Board settings",
|
||||
"calendarSettings": "Calendar settings",
|
||||
@ -1250,4 +1254,4 @@
|
||||
"userIcon": "User icon"
|
||||
},
|
||||
"noLogFiles": "There're no log files"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user