mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: revamp mobile database tab bar (#4160)
This commit is contained in:
parent
5ef9d55dca
commit
eef34caf27
@ -1610,8 +1610,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
) async {
|
) async {
|
||||||
final field = find.byWidgetPredicate(
|
final field = find.byWidgetPredicate(
|
||||||
(widget) =>
|
(widget) =>
|
||||||
widget is DesktopDatabasePropertyCell &&
|
widget is DatabasePropertyCell && widget.fieldInfo.name == fieldName,
|
||||||
widget.fieldInfo.name == fieldName,
|
|
||||||
);
|
);
|
||||||
final toggleVisibilityButton =
|
final toggleVisibilityButton =
|
||||||
find.descendant(of: field, matching: find.byType(FlowyIconButton));
|
find.descendant(of: field, matching: find.byType(FlowyIconButton));
|
||||||
|
@ -106,9 +106,11 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
|||||||
|
|
||||||
Widget _buildApp(ViewPB? view, List<Widget> actions, Widget child) {
|
Widget _buildApp(ViewPB? view, List<Widget> actions, Widget child) {
|
||||||
final icon = view?.icon.value;
|
final icon = view?.icon.value;
|
||||||
|
final elevation = (view?.layout.isDatabaseView ?? false) ? 0.0 : null;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
titleSpacing: 0,
|
titleSpacing: 0,
|
||||||
|
elevation: elevation,
|
||||||
title: Row(
|
title: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@ -118,9 +120,9 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
|||||||
fontSize: 22.0,
|
fontSize: 22.0,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: FlowyText.regular(
|
child: FlowyText.medium(
|
||||||
view?.name ?? widget.title ?? '',
|
view?.name ?? widget.title ?? '',
|
||||||
fontSize: 14.0,
|
fontSize: 15.0,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -4,7 +4,6 @@ import 'package:appflowy/mobile/presentation/database/board/board.dart';
|
|||||||
import 'package:appflowy/mobile/presentation/database/board/widgets/group_card_header.dart';
|
import 'package:appflowy/mobile/presentation/database/board/widgets/group_card_header.dart';
|
||||||
import 'package:appflowy/mobile/presentation/database/card/card.dart';
|
import 'package:appflowy/mobile/presentation/database/card/card.dart';
|
||||||
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
|
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/card/card.dart';
|
import 'package:appflowy/plugins/database_view/widgets/card/card.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart';
|
import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart';
|
||||||
@ -57,7 +56,7 @@ class _MobileBoardContentState extends State<MobileBoardContent> {
|
|||||||
final config = AppFlowyBoardConfig(
|
final config = AppFlowyBoardConfig(
|
||||||
groupCornerRadius: 8,
|
groupCornerRadius: 8,
|
||||||
groupBackgroundColor: Theme.of(context).colorScheme.secondary,
|
groupBackgroundColor: Theme.of(context).colorScheme.secondary,
|
||||||
groupMargin: const EdgeInsets.fromLTRB(4, 8, 4, 12),
|
groupMargin: const EdgeInsets.fromLTRB(4, 0, 4, 12),
|
||||||
groupHeaderPadding: const EdgeInsets.all(8),
|
groupHeaderPadding: const EdgeInsets.all(8),
|
||||||
groupBodyPadding: const EdgeInsets.all(4),
|
groupBodyPadding: const EdgeInsets.all(4),
|
||||||
groupFooterPadding: const EdgeInsets.all(8),
|
groupFooterPadding: const EdgeInsets.all(8),
|
||||||
@ -82,47 +81,33 @@ class _MobileBoardContentState extends State<MobileBoardContent> {
|
|||||||
final showCreateGroupButton =
|
final showCreateGroupButton =
|
||||||
context.read<BoardBloc>().groupingFieldType.canCreateNewGroup;
|
context.read<BoardBloc>().groupingFieldType.canCreateNewGroup;
|
||||||
final showHiddenGroups = state.hiddenGroups.isNotEmpty;
|
final showHiddenGroups = state.hiddenGroups.isNotEmpty;
|
||||||
return Column(
|
return AppFlowyBoard(
|
||||||
children: [
|
boardScrollController: scrollManager,
|
||||||
Divider(
|
scrollController: scrollController,
|
||||||
height: 1,
|
controller: context.read<BoardBloc>().boardController,
|
||||||
thickness: 1,
|
groupConstraints: BoxConstraints.tightFor(width: screenWidth * 0.7),
|
||||||
indent: GridSize.leadingHeaderPadding / 2,
|
config: config,
|
||||||
endIndent: GridSize.leadingHeaderPadding / 2,
|
leading: showHiddenGroups
|
||||||
|
? MobileHiddenGroupsColumn(
|
||||||
|
padding: config.groupHeaderPadding,
|
||||||
|
)
|
||||||
|
: const HSpace(16),
|
||||||
|
trailing: showCreateGroupButton
|
||||||
|
? const MobileBoardTrailing()
|
||||||
|
: const HSpace(16),
|
||||||
|
headerBuilder: (_, groupData) => BlocProvider<BoardBloc>.value(
|
||||||
|
value: context.read<BoardBloc>(),
|
||||||
|
child: GroupCardHeader(
|
||||||
|
groupData: groupData,
|
||||||
),
|
),
|
||||||
Expanded(
|
),
|
||||||
child: AppFlowyBoard(
|
footerBuilder: _buildFooter,
|
||||||
boardScrollController: scrollManager,
|
cardBuilder: (_, column, columnItem) => _buildCard(
|
||||||
scrollController: scrollController,
|
context: context,
|
||||||
controller: context.read<BoardBloc>().boardController,
|
afGroupData: column,
|
||||||
groupConstraints:
|
afGroupItem: columnItem,
|
||||||
BoxConstraints.tightFor(width: screenWidth * 0.7),
|
cardMargin: config.cardMargin,
|
||||||
config: config,
|
),
|
||||||
leading: showHiddenGroups
|
|
||||||
? MobileHiddenGroupsColumn(
|
|
||||||
padding: config.groupHeaderPadding,
|
|
||||||
)
|
|
||||||
: const HSpace(16),
|
|
||||||
trailing: showCreateGroupButton
|
|
||||||
? const MobileBoardTrailing()
|
|
||||||
: const HSpace(16),
|
|
||||||
headerBuilder: (_, groupData) =>
|
|
||||||
BlocProvider<BoardBloc>.value(
|
|
||||||
value: context.read<BoardBloc>(),
|
|
||||||
child: GroupCardHeader(
|
|
||||||
groupData: groupData,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
footerBuilder: _buildFooter,
|
|
||||||
cardBuilder: (_, column, columnItem) => _buildCard(
|
|
||||||
context: context,
|
|
||||||
afGroupData: column,
|
|
||||||
afGroupItem: columnItem,
|
|
||||||
cardMargin: config.cardMargin,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -25,7 +25,7 @@ class _MobileBoardTrailingState extends State<MobileBoardTrailing> {
|
|||||||
final style = Theme.of(context);
|
final style = Theme.of(context);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.all(8),
|
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: screenSize.width * 0.7,
|
width: screenSize.width * 0.7,
|
||||||
child: isEditing
|
child: isEditing
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.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_view/application/cell/cell_service.dart';
|
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
@ -141,13 +142,13 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
|
|||||||
showMobileBottomSheet(
|
showMobileBottomSheet(
|
||||||
context,
|
context,
|
||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 32),
|
padding: const EdgeInsets.only(top: 8, bottom: 36),
|
||||||
builder: (_) => Column(
|
builder: (_) => Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: _CardActionButton(
|
child: MobileQuickActionButton(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
final rowId = _bloc.state.currentRowId;
|
final rowId = _bloc.state.currentRowId;
|
||||||
if (rowId == null) {
|
if (rowId == null) {
|
||||||
@ -169,7 +170,7 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
|
|||||||
const Divider(height: 9),
|
const Divider(height: 9),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: _CardActionButton(
|
child: MobileQuickActionButton(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
final rowId = _bloc.state.currentRowId;
|
final rowId = _bloc.state.currentRowId;
|
||||||
if (rowId == null) {
|
if (rowId == null) {
|
||||||
@ -196,39 +197,6 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CardActionButton extends StatelessWidget {
|
|
||||||
const _CardActionButton({
|
|
||||||
required this.onTap,
|
|
||||||
required this.icon,
|
|
||||||
required this.text,
|
|
||||||
this.color,
|
|
||||||
});
|
|
||||||
|
|
||||||
final VoidCallback onTap;
|
|
||||||
final FlowySvgData icon;
|
|
||||||
final String text;
|
|
||||||
final Color? color;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return InkWell(
|
|
||||||
onTap: onTap,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
child: Container(
|
|
||||||
height: 44,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
FlowySvg(icon, size: const Size.square(20), color: color),
|
|
||||||
const HSpace(8),
|
|
||||||
FlowyText(text, fontSize: 15, color: color),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RowDetailFab extends StatelessWidget {
|
class RowDetailFab extends StatelessWidget {
|
||||||
const RowDetailFab({
|
const RowDetailFab({
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/database/field/bottom_sheet_create_field.dart';
|
import 'package:appflowy/mobile/presentation/database/field/mobile_field_bottom_sheets.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
@ -22,13 +22,10 @@ class OptionTextField extends StatelessWidget {
|
|||||||
controller: controller,
|
controller: controller,
|
||||||
textFieldPadding: const EdgeInsets.symmetric(horizontal: 12.0),
|
textFieldPadding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||||
onTextChanged: onTextChanged,
|
onTextChanged: onTextChanged,
|
||||||
leftIcon: Padding(
|
leftIcon: FlowySvg(
|
||||||
padding: const EdgeInsets.only(left: 16.0),
|
type.svgData,
|
||||||
child: FlowySvg(
|
size: const Size.square(36.0),
|
||||||
type.svgData,
|
blendMode: null,
|
||||||
size: const Size.square(36.0),
|
|
||||||
blendMode: null,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -180,24 +180,24 @@ class _DateCellEditBody extends StatelessWidget {
|
|||||||
showTopBorder: false,
|
showTopBorder: false,
|
||||||
child: _IncludeTimePicker(),
|
child: _IncludeTimePicker(),
|
||||||
),
|
),
|
||||||
_ColoredDivider(),
|
_Divider(),
|
||||||
FlowyOptionDecorateBox(
|
FlowyOptionDecorateBox(
|
||||||
child: MobileDatePicker(),
|
child: MobileDatePicker(),
|
||||||
),
|
),
|
||||||
_ColoredDivider(),
|
_Divider(),
|
||||||
_EndDateSwitch(),
|
_EndDateSwitch(),
|
||||||
_IncludeTimeSwitch(),
|
_IncludeTimeSwitch(),
|
||||||
_ColoredDivider(),
|
_Divider(),
|
||||||
_ClearDateButton(),
|
_ClearDateButton(),
|
||||||
_ColoredDivider(),
|
_Divider(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ColoredDivider extends StatelessWidget {
|
class _Divider extends StatelessWidget {
|
||||||
const _ColoredDivider();
|
const _Divider();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -364,7 +364,7 @@ class _EndDateSwitch extends StatelessWidget {
|
|||||||
return BlocSelector<DateCellCalendarBloc, DateCellCalendarState, bool>(
|
return BlocSelector<DateCellCalendarBloc, DateCellCalendarState, bool>(
|
||||||
selector: (state) => state.isRange,
|
selector: (state) => state.isRange,
|
||||||
builder: (context, isRange) {
|
builder: (context, isRange) {
|
||||||
return FlowyOptionTile.switcher(
|
return FlowyOptionTile.toggle(
|
||||||
text: LocaleKeys.grid_field_isRange.tr(),
|
text: LocaleKeys.grid_field_isRange.tr(),
|
||||||
isSelected: isRange,
|
isSelected: isRange,
|
||||||
onValueChanged: (value) {
|
onValueChanged: (value) {
|
||||||
@ -386,7 +386,7 @@ class _IncludeTimeSwitch extends StatelessWidget {
|
|||||||
return BlocSelector<DateCellCalendarBloc, DateCellCalendarState, bool>(
|
return BlocSelector<DateCellCalendarBloc, DateCellCalendarState, bool>(
|
||||||
selector: (state) => state.includeTime,
|
selector: (state) => state.includeTime,
|
||||||
builder: (context, includeTime) {
|
builder: (context, includeTime) {
|
||||||
return FlowyOptionTile.switcher(
|
return FlowyOptionTile.toggle(
|
||||||
showTopBorder: false,
|
showTopBorder: false,
|
||||||
text: LocaleKeys.grid_field_includeTime.tr(),
|
text: LocaleKeys.grid_field_includeTime.tr(),
|
||||||
isSelected: includeTime,
|
isSelected: includeTime,
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||||
import 'package:appflowy/mobile/presentation/database/field/mobile_field_type_grid.dart';
|
|
||||||
import 'package:appflowy/mobile/presentation/database/field/mobile_field_type_option_editor.dart';
|
|
||||||
import 'package:appflowy/mobile/presentation/database/field/quick_edit_field_bottom_sheet.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -9,6 +6,9 @@ import 'package:go_router/go_router.dart';
|
|||||||
|
|
||||||
import 'mobile_create_field_screen.dart';
|
import 'mobile_create_field_screen.dart';
|
||||||
import 'mobile_edit_field_screen.dart';
|
import 'mobile_edit_field_screen.dart';
|
||||||
|
import 'mobile_field_type_grid.dart';
|
||||||
|
import 'mobile_field_type_option_editor.dart';
|
||||||
|
import 'mobile_quick_field_editor.dart';
|
||||||
|
|
||||||
void showCreateFieldBottomSheet(BuildContext context, String viewId) {
|
void showCreateFieldBottomSheet(BuildContext context, String viewId) {
|
||||||
showMobileBottomSheet(
|
showMobileBottomSheet(
|
||||||
@ -95,13 +95,10 @@ void showQuickEditField(
|
|||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
resizeToAvoidBottomInset: true,
|
resizeToAvoidBottomInset: true,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return ConstrainedBox(
|
return SingleChildScrollView(
|
||||||
constraints: const BoxConstraints(maxHeight: 500),
|
child: QuickEditField(
|
||||||
child: SingleChildScrollView(
|
viewId: viewId,
|
||||||
child: QuickEditField(
|
fieldInfo: fieldInfo,
|
||||||
viewId: viewId,
|
|
||||||
fieldInfo: fieldInfo,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
@ -440,12 +440,10 @@ class _DateOptionState extends State<_DateOption> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 4.0),
|
||||||
vertical: 6.0,
|
|
||||||
horizontal: 16.0,
|
|
||||||
),
|
|
||||||
child: FlowyText(
|
child: FlowyText(
|
||||||
LocaleKeys.grid_field_dateFormat.tr(),
|
LocaleKeys.grid_field_dateFormat.tr().toUpperCase(),
|
||||||
|
fontSize: 13,
|
||||||
color: Theme.of(context).hintColor,
|
color: Theme.of(context).hintColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -496,12 +494,10 @@ class _TimeOptionState extends State<_TimeOption> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 4.0),
|
||||||
vertical: 6.0,
|
|
||||||
horizontal: 16.0,
|
|
||||||
),
|
|
||||||
child: FlowyText(
|
child: FlowyText(
|
||||||
LocaleKeys.grid_field_timeFormat.tr(),
|
LocaleKeys.grid_field_timeFormat.tr().toUpperCase(),
|
||||||
|
fontSize: 13,
|
||||||
color: Theme.of(context).hintColor,
|
color: Theme.of(context).hintColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -629,12 +625,10 @@ class _SelectOption extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 4.0),
|
||||||
vertical: 6.0,
|
|
||||||
horizontal: 16.0,
|
|
||||||
),
|
|
||||||
child: FlowyText(
|
child: FlowyText(
|
||||||
LocaleKeys.grid_field_optionTitle.tr(),
|
LocaleKeys.grid_field_optionTitle.tr().toUpperCase(),
|
||||||
|
fontSize: 13,
|
||||||
color: Theme.of(context).hintColor,
|
color: Theme.of(context).hintColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -770,7 +764,10 @@ class __SelectOptionTileState extends State<_SelectOptionTile> {
|
|||||||
textFieldHintText: LocaleKeys.grid_field_typeANewOption.tr(),
|
textFieldHintText: LocaleKeys.grid_field_typeANewOption.tr(),
|
||||||
showTopBorder: widget.showTopBorder,
|
showTopBorder: widget.showTopBorder,
|
||||||
showBottomBorder: widget.showBottomBorder,
|
showBottomBorder: widget.showBottomBorder,
|
||||||
textFieldPadding: const EdgeInsets.symmetric(horizontal: 16.0),
|
textFieldPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 0.0,
|
||||||
|
vertical: 16.0,
|
||||||
|
),
|
||||||
trailing: _SelectOptionColor(
|
trailing: _SelectOptionColor(
|
||||||
color: option.color,
|
color: option.color,
|
||||||
onChanged: (color) {
|
onChanged: (color) {
|
||||||
|
@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
|
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
|
||||||
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/widgets.dart';
|
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/widgets.dart';
|
||||||
import 'package:appflowy/mobile/presentation/database/field/bottom_sheet_create_field.dart';
|
import 'package:appflowy/mobile/presentation/database/field/mobile_field_bottom_sheets.dart';
|
||||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_backend_service.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_backend_service.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||||
@ -82,15 +82,16 @@ class _QuickEditFieldState extends State<QuickEditField> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
FlowyOptionTile.text(
|
if (!widget.fieldInfo.isPrimary)
|
||||||
showTopBorder: false,
|
FlowyOptionTile.text(
|
||||||
text: LocaleKeys.grid_field_hide.tr(),
|
showTopBorder: false,
|
||||||
leftIcon: const FlowySvg(FlowySvgs.hide_s),
|
text: LocaleKeys.grid_field_hide.tr(),
|
||||||
onTap: () async {
|
leftIcon: const FlowySvg(FlowySvgs.hide_s),
|
||||||
context.pop();
|
onTap: () async {
|
||||||
await service.hide();
|
context.pop();
|
||||||
},
|
await service.hide();
|
||||||
),
|
},
|
||||||
|
),
|
||||||
FlowyOptionTile.text(
|
FlowyOptionTile.text(
|
||||||
showTopBorder: false,
|
showTopBorder: false,
|
||||||
text: LocaleKeys.grid_field_insertLeft.tr(),
|
text: LocaleKeys.grid_field_insertLeft.tr(),
|
||||||
@ -133,6 +134,7 @@ class _QuickEditFieldState extends State<QuickEditField> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
const VSpace(12),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -0,0 +1,245 @@
|
|||||||
|
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_view/application/database_controller.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/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_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||||
|
import 'package:collection/collection.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';
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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: viewPB,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
width: iconWidth,
|
||||||
|
iconPadding: EdgeInsets.zero,
|
||||||
|
onPressed: () => Navigator.of(context).maybePop(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: FlowyText.medium(
|
||||||
|
LocaleKeys.grid_settings_properties.tr(),
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MobileDatabaseFieldListBody extends StatelessWidget {
|
||||||
|
const _MobileDatabaseFieldListBody({
|
||||||
|
required this.databaseController,
|
||||||
|
required this.view,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DatabaseController databaseController;
|
||||||
|
final ViewPB view;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider<DatabasePropertyBloc>(
|
||||||
|
create: (_) => DatabasePropertyBloc(
|
||||||
|
viewId: view.id,
|
||||||
|
fieldController: databaseController.fieldController,
|
||||||
|
)..add(const DatabasePropertyEvent.initial()),
|
||||||
|
child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final cells = state.fieldContexts
|
||||||
|
.mapIndexed(
|
||||||
|
(index, field) => DatabaseFieldListTile(
|
||||||
|
key: ValueKey(field.id),
|
||||||
|
viewId: view.id,
|
||||||
|
fieldController: databaseController.fieldController,
|
||||||
|
fieldInfo: field,
|
||||||
|
index: index,
|
||||||
|
showTopBorder: index == 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return ReorderableListView.builder(
|
||||||
|
proxyDecorator: (_, index, anim) {
|
||||||
|
final field = state.fieldContexts[index];
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: anim,
|
||||||
|
builder: (BuildContext context, Widget? child) {
|
||||||
|
final double animValue =
|
||||||
|
Curves.easeInOut.transform(anim.value);
|
||||||
|
final double scale = lerpDouble(1, 1.05, animValue)!;
|
||||||
|
return Transform.scale(
|
||||||
|
scale: scale,
|
||||||
|
child: Material(
|
||||||
|
child: DatabaseFieldListTile(
|
||||||
|
key: ValueKey(field.id),
|
||||||
|
viewId: view.id,
|
||||||
|
fieldController: databaseController.fieldController,
|
||||||
|
fieldInfo: field,
|
||||||
|
index: index,
|
||||||
|
showTopBorder: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
buildDefaultDragHandles: true,
|
||||||
|
shrinkWrap: true,
|
||||||
|
onReorder: (from, to) {
|
||||||
|
context
|
||||||
|
.read<DatabasePropertyBloc>()
|
||||||
|
.add(DatabasePropertyEvent.moveField(from, to));
|
||||||
|
},
|
||||||
|
footer: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_divider(),
|
||||||
|
_NewDatabaseFieldTile(viewId: view.id),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
itemCount: cells.length,
|
||||||
|
itemBuilder: (context, index) => cells[index],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _divider() => const VSpace(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DatabaseFieldListTile extends StatelessWidget {
|
||||||
|
const DatabaseFieldListTile({
|
||||||
|
super.key,
|
||||||
|
required this.index,
|
||||||
|
required this.fieldInfo,
|
||||||
|
required this.viewId,
|
||||||
|
required this.fieldController,
|
||||||
|
required this.showTopBorder,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int index;
|
||||||
|
final FieldInfo fieldInfo;
|
||||||
|
final String viewId;
|
||||||
|
final FieldController fieldController;
|
||||||
|
final bool showTopBorder;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (fieldInfo.field.isPrimary) {
|
||||||
|
return FlowyOptionTile.text(
|
||||||
|
text: fieldInfo.name,
|
||||||
|
leftIcon: FlowySvg(
|
||||||
|
fieldInfo.fieldType.icon(),
|
||||||
|
size: const Size.square(20),
|
||||||
|
),
|
||||||
|
showTopBorder: showTopBorder,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return FlowyOptionTile.toggle(
|
||||||
|
isSelected: fieldInfo.visibility?.isVisibleState() ?? false,
|
||||||
|
text: fieldInfo.name,
|
||||||
|
leftIcon: FlowySvg(
|
||||||
|
fieldInfo.fieldType.icon(),
|
||||||
|
size: const Size.square(20),
|
||||||
|
),
|
||||||
|
showTopBorder: showTopBorder,
|
||||||
|
onValueChanged: (value) {
|
||||||
|
final newVisibility = fieldInfo.visibility!.toggle();
|
||||||
|
context.read<DatabasePropertyBloc>().add(
|
||||||
|
DatabasePropertyEvent.setFieldVisibility(
|
||||||
|
fieldInfo.id,
|
||||||
|
newVisibility,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NewDatabaseFieldTile extends StatelessWidget {
|
||||||
|
const _NewDatabaseFieldTile({required this.viewId});
|
||||||
|
|
||||||
|
final String viewId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FlowyOptionTile.text(
|
||||||
|
text: LocaleKeys.grid_field_newProperty.tr(),
|
||||||
|
leftIcon: FlowySvg(
|
||||||
|
FlowySvgs.add_s,
|
||||||
|
size: const Size.square(20),
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
textColor: Theme.of(context).hintColor,
|
||||||
|
onTap: () => showCreateFieldBottomSheet(context, viewId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
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/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';
|
||||||
|
|
||||||
|
/// [DatabaseViewLayoutPicker] is seen when changing the layout type of a
|
||||||
|
/// database view or creating a new database view.
|
||||||
|
class DatabaseViewLayoutPicker extends StatelessWidget {
|
||||||
|
const DatabaseViewLayoutPicker({
|
||||||
|
super.key,
|
||||||
|
required this.selectedLayout,
|
||||||
|
required this.onSelect,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DatabaseLayoutPB selectedLayout;
|
||||||
|
final void Function(DatabaseLayoutPB layout) onSelect;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildButton(DatabaseLayoutPB.Grid, true),
|
||||||
|
_buildButton(DatabaseLayoutPB.Board, false),
|
||||||
|
_buildButton(DatabaseLayoutPB.Calendar, false),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildButton(DatabaseLayoutPB layout, bool showTopBorder) {
|
||||||
|
return FlowyOptionTile.checkbox(
|
||||||
|
text: layout.layoutName,
|
||||||
|
leftIcon: FlowySvg(layout.icon, size: const Size.square(20)),
|
||||||
|
isSelected: selectedLayout == layout,
|
||||||
|
showTopBorder: showTopBorder,
|
||||||
|
onTap: () {
|
||||||
|
onSelect(layout);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MobileCalendarViewLayoutSettings extends StatelessWidget {
|
||||||
|
const MobileCalendarViewLayoutSettings({
|
||||||
|
super.key,
|
||||||
|
required this.databaseController,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DatabaseController databaseController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_layoutField(),
|
||||||
|
_divider(),
|
||||||
|
..._startWeek(context),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _layoutField() {
|
||||||
|
return FlowyOptionTile.text(
|
||||||
|
text: LocaleKeys.calendar_settings_layoutDateField.tr(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _divider() => const VSpace(20);
|
||||||
|
|
||||||
|
List<Widget> _startWeek(BuildContext context) {
|
||||||
|
final symbols = DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
|
||||||
|
return [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 4.0),
|
||||||
|
child: FlowyText(
|
||||||
|
LocaleKeys.calendar_settings_firstDayOfWeek.tr().toUpperCase(),
|
||||||
|
fontSize: 13,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FlowyOptionTile.checkbox(
|
||||||
|
text: symbols.WEEKDAYS[-1],
|
||||||
|
isSelected: true,
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
FlowyOptionTile.checkbox(
|
||||||
|
text: symbols.WEEKDAYS[0],
|
||||||
|
isSelected: false,
|
||||||
|
showTopBorder: false,
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MobileBoardViewLayoutSettings extends StatelessWidget {
|
||||||
|
const MobileBoardViewLayoutSettings({
|
||||||
|
super.key,
|
||||||
|
required this.databaseController,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DatabaseController databaseController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FlowyOptionTile.text(text: LocaleKeys.board_groupBy.tr());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.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/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';
|
||||||
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'database_view_layout.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});
|
||||||
|
|
||||||
|
final List<ViewPB> views;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final children = [
|
||||||
|
...views.map((view) => MobileDatabaseViewListButton(view: view)),
|
||||||
|
const VSpace(20),
|
||||||
|
const MobileNewDatabaseViewButton(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
class MobileDatabaseViewListButton extends StatelessWidget {
|
||||||
|
const MobileDatabaseViewListButton({super.key, required this.view});
|
||||||
|
|
||||||
|
final ViewPB view;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FlowyOptionTile.text(
|
||||||
|
text: view.name,
|
||||||
|
onTap: () {},
|
||||||
|
leftIcon: _buildViewIconButton(context, view),
|
||||||
|
trailing: FlowySvg(
|
||||||
|
FlowySvgs.three_dots_s,
|
||||||
|
size: const Size.square(20),
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildViewIconButton(BuildContext context, ViewPB view) {
|
||||||
|
return view.icon.value.isNotEmpty
|
||||||
|
? EmojiText(
|
||||||
|
emoji: view.icon.value,
|
||||||
|
fontSize: 16.0,
|
||||||
|
)
|
||||||
|
: SizedBox.square(
|
||||||
|
dimension: 20.0,
|
||||||
|
child: view.defaultIcon(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MobileNewDatabaseViewButton extends StatelessWidget {
|
||||||
|
const MobileNewDatabaseViewButton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FlowyOptionTile.text(
|
||||||
|
text: LocaleKeys.grid_settings_createView.tr(),
|
||||||
|
textColor: Theme.of(context).hintColor,
|
||||||
|
leftIcon: FlowySvg(
|
||||||
|
FlowySvgs.add_s,
|
||||||
|
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: () {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MobileCreateDatabaseView extends StatefulWidget {
|
||||||
|
const MobileCreateDatabaseView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MobileCreateDatabaseView> createState() =>
|
||||||
|
_MobileCreateDatabaseViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
FlowyOptionTile.textField(
|
||||||
|
controller: controller,
|
||||||
|
leftIcon: _buildViewIcon(),
|
||||||
|
),
|
||||||
|
const VSpace(20),
|
||||||
|
DatabaseViewLayoutPicker(
|
||||||
|
selectedLayout: DatabaseLayoutPB.Grid,
|
||||||
|
onSelect: (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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
child: Center(child: viewIcon),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
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:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// [MobileDatabaseViewQuickActions] is gives users to quickly edit a database
|
||||||
|
/// view from the [MobileDatabaseViewList]
|
||||||
|
class MobileDatabaseViewQuickActions extends StatelessWidget {
|
||||||
|
const MobileDatabaseViewQuickActions({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_actionButton(_Action.edit),
|
||||||
|
_divider(),
|
||||||
|
_actionButton(_Action.duplicate),
|
||||||
|
_divider(),
|
||||||
|
_actionButton(_Action.delete),
|
||||||
|
_divider(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _actionButton(_Action action) {
|
||||||
|
return MobileQuickActionButton(
|
||||||
|
icon: action.icon,
|
||||||
|
text: action.label,
|
||||||
|
onTap: () {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _divider() => const Divider(height: 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum _Action {
|
||||||
|
edit,
|
||||||
|
duplicate,
|
||||||
|
delete;
|
||||||
|
|
||||||
|
String get label {
|
||||||
|
return switch (this) {
|
||||||
|
edit => LocaleKeys.grid_settings_editView.tr(),
|
||||||
|
duplicate => LocaleKeys.button_duplicate.tr(),
|
||||||
|
delete => LocaleKeys.button_delete.tr(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
FlowySvgData get icon {
|
||||||
|
return switch (this) {
|
||||||
|
edit => FlowySvgs.grid_s,
|
||||||
|
duplicate => FlowySvgs.copy_s,
|
||||||
|
delete => FlowySvgs.delete_s,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Color? color(BuildContext context) {
|
||||||
|
return switch (this) {
|
||||||
|
delete => Theme.of(context).colorScheme.error,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
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,
|
||||||
|
}
|
@ -0,0 +1,441 @@
|
|||||||
|
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/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';
|
||||||
|
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_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 {
|
||||||
|
const MobileEditDatabaseViewScreen({
|
||||||
|
super.key,
|
||||||
|
required this.databaseController,
|
||||||
|
required this.viewPB,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DatabaseController databaseController;
|
||||||
|
final ViewPB viewPB;
|
||||||
|
|
||||||
|
@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,
|
||||||
|
viewPB: widget.viewPB,
|
||||||
|
),
|
||||||
|
MobileEditDatabaseViewPageEnum.fields => _wrapSubPage(
|
||||||
|
context,
|
||||||
|
MobileDatabaseFieldList(
|
||||||
|
databaseController: widget.databaseController,
|
||||||
|
viewPB: widget.viewPB,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_ => 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,
|
||||||
|
required this.viewPB,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DatabaseController databaseController;
|
||||||
|
final ViewPB viewPB;
|
||||||
|
|
||||||
|
@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,
|
||||||
|
view: viewPB,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
width: iconWidth,
|
||||||
|
iconPadding: EdgeInsets.zero,
|
||||||
|
onPressed: () => context.pop(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: FlowyText.medium(
|
||||||
|
LocaleKeys.grid_settings_editView.tr(),
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_NameAndIcon(view: state.view),
|
||||||
|
_divider(),
|
||||||
|
DatabaseViewSettingTile(
|
||||||
|
setting: DatabaseViewSettings.layout,
|
||||||
|
databaseController: databaseController,
|
||||||
|
view: state.view,
|
||||||
|
showTopBorder: true,
|
||||||
|
),
|
||||||
|
DatabaseViewSettingTile(
|
||||||
|
setting: DatabaseViewSettings.fields,
|
||||||
|
databaseController: databaseController,
|
||||||
|
view: state.view,
|
||||||
|
),
|
||||||
|
_divider(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _divider() => const VSpace(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NameAndIcon extends StatefulWidget {
|
||||||
|
const _NameAndIcon({required this.view});
|
||||||
|
|
||||||
|
final ViewPB view;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_NameAndIcon> createState() => _NameAndIconState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NameAndIconState extends State<_NameAndIcon> {
|
||||||
|
final TextEditingController textEditingController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
textEditingController.text = widget.view.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DatabaseViewSettings {
|
||||||
|
layout,
|
||||||
|
fields,
|
||||||
|
filter,
|
||||||
|
sort,
|
||||||
|
board,
|
||||||
|
calendar,
|
||||||
|
duplicate,
|
||||||
|
delete;
|
||||||
|
|
||||||
|
String get label {
|
||||||
|
return switch (this) {
|
||||||
|
layout => LocaleKeys.grid_settings_databaseLayout.tr(),
|
||||||
|
fields => LocaleKeys.grid_settings_properties.tr(),
|
||||||
|
filter => LocaleKeys.grid_settings_filter.tr(),
|
||||||
|
sort => LocaleKeys.grid_settings_sort.tr(),
|
||||||
|
board => LocaleKeys.grid_settings_boardSettings.tr(),
|
||||||
|
calendar => LocaleKeys.grid_settings_calendarSettings.tr(),
|
||||||
|
duplicate => LocaleKeys.grid_settings_duplicateView.tr(),
|
||||||
|
delete => LocaleKeys.grid_settings_deleteView.tr(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
FlowySvgData get icon {
|
||||||
|
return switch (this) {
|
||||||
|
layout => FlowySvgs.card_view_s,
|
||||||
|
fields => FlowySvgs.disorder_list_s,
|
||||||
|
filter => FlowySvgs.filter_s,
|
||||||
|
sort => FlowySvgs.sort_ascending_s,
|
||||||
|
board => FlowySvgs.board_s,
|
||||||
|
calendar => FlowySvgs.date_s,
|
||||||
|
duplicate => FlowySvgs.copy_s,
|
||||||
|
delete => FlowySvgs.delete_s,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileEditDatabaseViewPageEnum? get subPage {
|
||||||
|
return switch (this) {
|
||||||
|
fields => MobileEditDatabaseViewPageEnum.fields,
|
||||||
|
filter => MobileEditDatabaseViewPageEnum.filter,
|
||||||
|
sort => MobileEditDatabaseViewPageEnum.sort,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DatabaseViewSettingTile extends StatelessWidget {
|
||||||
|
const DatabaseViewSettingTile({
|
||||||
|
super.key,
|
||||||
|
required this.setting,
|
||||||
|
required this.databaseController,
|
||||||
|
required this.view,
|
||||||
|
this.showTopBorder = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DatabaseViewSettings setting;
|
||||||
|
final DatabaseController databaseController;
|
||||||
|
final ViewPB view;
|
||||||
|
final bool showTopBorder;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FlowyOptionTile.text(
|
||||||
|
text: setting.label,
|
||||||
|
leftIcon: FlowySvg(setting.icon, size: const Size.square(20)),
|
||||||
|
trailing: _trailing(context, setting, view, databaseController),
|
||||||
|
showTopBorder: showTopBorder,
|
||||||
|
onTap: () => _onTap(context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _trailing(
|
||||||
|
BuildContext context,
|
||||||
|
DatabaseViewSettings setting,
|
||||||
|
ViewPB view,
|
||||||
|
DatabaseController databaseController,
|
||||||
|
) {
|
||||||
|
switch (setting) {
|
||||||
|
case DatabaseViewSettings.layout:
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
FlowyText(
|
||||||
|
databaseLayoutFromViewLayout(view.layout).layoutName,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
const HSpace(8),
|
||||||
|
const FlowySvg(FlowySvgs.arrow_right_s),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
case DatabaseViewSettings.fields:
|
||||||
|
final numVisible = databaseController.fieldController.fieldInfos
|
||||||
|
.where((field) => field.visibility != FieldVisibility.AlwaysHidden)
|
||||||
|
.length;
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
FlowyText(
|
||||||
|
"$numVisible shown",
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
const HSpace(8),
|
||||||
|
const FlowySvg(FlowySvgs.arrow_right_s),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
|
builder: (context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 24, bottom: 46),
|
||||||
|
child: DatabaseViewLayoutPicker(
|
||||||
|
selectedLayout: databaseLayout,
|
||||||
|
onSelect: (layout) {
|
||||||
|
Navigator.of(context).pop(layout);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (newLayout != null && newLayout != databaseLayout) {
|
||||||
|
DatabaseViewBackendService.updateLayout(
|
||||||
|
viewId: databaseController.viewId,
|
||||||
|
layout: newLayout,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setting == DatabaseViewSettings.board) {
|
||||||
|
showMobileBottomSheet<DatabaseLayoutPB>(
|
||||||
|
context,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
|
builder: (context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 24, bottom: 46),
|
||||||
|
child: MobileBoardViewLayoutSettings(
|
||||||
|
databaseController: databaseController,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setting == DatabaseViewSettings.calendar) {
|
||||||
|
showMobileBottomSheet<DatabaseLayoutPB>(
|
||||||
|
context,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
|
builder: (context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 24, bottom: 46),
|
||||||
|
child: MobileCalendarViewLayoutSettings(
|
||||||
|
databaseController: databaseController,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setting == DatabaseViewSettings.delete) {
|
||||||
|
context.read<ViewBloc>().add(const ViewEvent.delete());
|
||||||
|
context.pop(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class MobileQuickActionButton extends StatelessWidget {
|
||||||
|
const MobileQuickActionButton({
|
||||||
|
super.key,
|
||||||
|
required this.onTap,
|
||||||
|
required this.icon,
|
||||||
|
required this.text,
|
||||||
|
this.color,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final FlowySvgData icon;
|
||||||
|
final String text;
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Container(
|
||||||
|
height: 44,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
FlowySvg(icon, size: const Size.square(20), color: color),
|
||||||
|
const HSpace(8),
|
||||||
|
FlowyText(text, fontSize: 15, color: color),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,16 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
enum FlowyOptionTileType {
|
enum FlowyOptionTileType {
|
||||||
text,
|
text,
|
||||||
textField,
|
textField,
|
||||||
checkbox,
|
checkbox,
|
||||||
|
toggle,
|
||||||
}
|
}
|
||||||
|
|
||||||
// used in cell editor
|
|
||||||
|
|
||||||
class FlowyOptionTile extends StatelessWidget {
|
class FlowyOptionTile extends StatelessWidget {
|
||||||
const FlowyOptionTile._({
|
const FlowyOptionTile._({
|
||||||
required this.type,
|
required this.type,
|
||||||
@ -59,8 +59,8 @@ class FlowyOptionTile extends StatelessWidget {
|
|||||||
void Function(String value)? onTextChanged,
|
void Function(String value)? onTextChanged,
|
||||||
void Function(String value)? onTextSubmitted,
|
void Function(String value)? onTextSubmitted,
|
||||||
EdgeInsets textFieldPadding = const EdgeInsets.symmetric(
|
EdgeInsets textFieldPadding = const EdgeInsets.symmetric(
|
||||||
horizontal: 12.0,
|
horizontal: 0.0,
|
||||||
vertical: 2.0,
|
vertical: 16.0,
|
||||||
),
|
),
|
||||||
bool showTopBorder = true,
|
bool showTopBorder = true,
|
||||||
bool showBottomBorder = true,
|
bool showBottomBorder = true,
|
||||||
@ -69,7 +69,7 @@ class FlowyOptionTile extends StatelessWidget {
|
|||||||
String? textFieldHintText,
|
String? textFieldHintText,
|
||||||
}) {
|
}) {
|
||||||
return FlowyOptionTile._(
|
return FlowyOptionTile._(
|
||||||
type: FlowyOptionTileType.text,
|
type: FlowyOptionTileType.textField,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
textFieldPadding: textFieldPadding,
|
textFieldPadding: textFieldPadding,
|
||||||
text: null,
|
text: null,
|
||||||
@ -88,6 +88,7 @@ class FlowyOptionTile extends StatelessWidget {
|
|||||||
required String text,
|
required String text,
|
||||||
required bool isSelected,
|
required bool isSelected,
|
||||||
required VoidCallback? onTap,
|
required VoidCallback? onTap,
|
||||||
|
Widget? leftIcon,
|
||||||
bool showTopBorder = true,
|
bool showTopBorder = true,
|
||||||
bool showBottomBorder = true,
|
bool showBottomBorder = true,
|
||||||
}) {
|
}) {
|
||||||
@ -98,6 +99,7 @@ class FlowyOptionTile extends StatelessWidget {
|
|||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
showTopBorder: showTopBorder,
|
showTopBorder: showTopBorder,
|
||||||
showBottomBorder: showBottomBorder,
|
showBottomBorder: showBottomBorder,
|
||||||
|
leading: leftIcon,
|
||||||
trailing: isSelected
|
trailing: isSelected
|
||||||
? const FlowySvg(
|
? const FlowySvg(
|
||||||
FlowySvgs.blue_check_s,
|
FlowySvgs.blue_check_s,
|
||||||
@ -108,7 +110,7 @@ class FlowyOptionTile extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory FlowyOptionTile.switcher({
|
factory FlowyOptionTile.toggle({
|
||||||
required String text,
|
required String text,
|
||||||
required bool isSelected,
|
required bool isSelected,
|
||||||
required void Function(bool value) onValueChanged,
|
required void Function(bool value) onValueChanged,
|
||||||
@ -117,14 +119,14 @@ class FlowyOptionTile extends StatelessWidget {
|
|||||||
Widget? leftIcon,
|
Widget? leftIcon,
|
||||||
}) {
|
}) {
|
||||||
return FlowyOptionTile._(
|
return FlowyOptionTile._(
|
||||||
type: FlowyOptionTileType.text,
|
type: FlowyOptionTileType.toggle,
|
||||||
text: text,
|
text: text,
|
||||||
controller: null,
|
controller: null,
|
||||||
onTap: null,
|
onTap: () => onValueChanged(!isSelected),
|
||||||
showTopBorder: showTopBorder,
|
showTopBorder: showTopBorder,
|
||||||
showBottomBorder: showBottomBorder,
|
showBottomBorder: showBottomBorder,
|
||||||
leading: leftIcon,
|
leading: leftIcon,
|
||||||
trailing: _Switcher(value: isSelected, onChanged: onValueChanged),
|
trailing: _Toggle(value: isSelected, onChanged: onValueChanged),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,110 +152,101 @@ class FlowyOptionTile extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final leadingWidget = _buildLeading();
|
||||||
|
|
||||||
final child = ColoredBox(
|
final child = ColoredBox(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
child: FlowyOptionDecorateBox(
|
child: FlowyOptionDecorateBox(
|
||||||
showTopBorder: showTopBorder,
|
showTopBorder: showTopBorder,
|
||||||
showBottomBorder: showBottomBorder,
|
showBottomBorder: showBottomBorder,
|
||||||
child: Row(
|
child: Padding(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
children: [
|
child: Row(
|
||||||
_buildText(),
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
..._buildTextField(),
|
children: [
|
||||||
if (controller == null) const Spacer(),
|
if (leadingWidget != null) leadingWidget,
|
||||||
trailing ?? const SizedBox.shrink(),
|
_buildText(),
|
||||||
const HSpace(12.0),
|
_buildTextField(),
|
||||||
],
|
if (trailing != null) trailing!,
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (type == FlowyOptionTileType.checkbox ||
|
if (type == FlowyOptionTileType.checkbox ||
|
||||||
|
type == FlowyOptionTileType.toggle ||
|
||||||
type == FlowyOptionTileType.text) {
|
type == FlowyOptionTileType.text) {
|
||||||
return FlowyButton(
|
return GestureDetector(
|
||||||
expandText: true,
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
text: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget? _buildLeading() {
|
||||||
|
if (leading != null) {
|
||||||
|
return Center(child: leading);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildText() {
|
Widget _buildText() {
|
||||||
if (text == null) {
|
if (text == null || type == FlowyOptionTileType.textField) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
final padding = EdgeInsets.symmetric(
|
||||||
case FlowyOptionTileType.text:
|
horizontal: leading == null ? 0.0 : 8.0,
|
||||||
return FlowyButton(
|
vertical: 16.0,
|
||||||
useIntrinsicWidth: true,
|
);
|
||||||
text: FlowyText(
|
|
||||||
text!,
|
|
||||||
color: textColor,
|
|
||||||
),
|
|
||||||
margin: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16.0,
|
|
||||||
vertical: 16.0,
|
|
||||||
),
|
|
||||||
leftIcon: leading,
|
|
||||||
leftIconSize: const Size.square(24.0),
|
|
||||||
iconPadding: 8.0,
|
|
||||||
onTap: onTap,
|
|
||||||
);
|
|
||||||
case FlowyOptionTileType.textField:
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
case FlowyOptionTileType.checkbox:
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16.0,
|
|
||||||
vertical: 16.0,
|
|
||||||
),
|
|
||||||
child: FlowyText(
|
|
||||||
text!,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _buildTextField() {
|
return Expanded(
|
||||||
if (controller == null) {
|
child: Padding(
|
||||||
return [
|
padding: padding,
|
||||||
const SizedBox.shrink(),
|
child: FlowyText(
|
||||||
];
|
text!,
|
||||||
}
|
fontSize: 15,
|
||||||
|
color: textColor,
|
||||||
return [
|
|
||||||
if (leading != null) leading!,
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
constraints: const BoxConstraints.tightFor(
|
|
||||||
height: 54.0,
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: TextField(
|
|
||||||
controller: controller,
|
|
||||||
textInputAction: TextInputAction.done,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
border: InputBorder.none,
|
|
||||||
enabledBorder: InputBorder.none,
|
|
||||||
focusedBorder: InputBorder.none,
|
|
||||||
contentPadding: textFieldPadding,
|
|
||||||
hintText: textFieldHintText,
|
|
||||||
),
|
|
||||||
onChanged: onTextChanged,
|
|
||||||
onSubmitted: onTextSubmitted,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTextField() {
|
||||||
|
if (controller == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
child: Container(
|
||||||
|
constraints: const BoxConstraints.tightFor(
|
||||||
|
height: 54.0,
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: TextField(
|
||||||
|
controller: controller,
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
enabledBorder: InputBorder.none,
|
||||||
|
focusedBorder: InputBorder.none,
|
||||||
|
contentPadding: textFieldPadding,
|
||||||
|
hintText: textFieldHintText,
|
||||||
|
),
|
||||||
|
onChanged: onTextChanged,
|
||||||
|
onSubmitted: onTextSubmitted,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Switcher extends StatelessWidget {
|
class _Toggle extends StatelessWidget {
|
||||||
const _Switcher({
|
const _Toggle({
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
});
|
});
|
||||||
@ -263,13 +256,16 @@ class _Switcher extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// CupertinoSwitch adds a 8px margin all around. The original size of the
|
||||||
|
// switch is 38 x 22.
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: 48,
|
width: 46,
|
||||||
|
height: 30,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.fill,
|
fit: BoxFit.fill,
|
||||||
child: Switch.adaptive(
|
child: CupertinoSwitch(
|
||||||
value: value,
|
value: value,
|
||||||
activeColor: const Color(0xFF00BCF0),
|
activeColor: Theme.of(context).colorScheme.primary,
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -11,6 +11,8 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
|||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
|
||||||
|
|
||||||
|
import 'layout/layout_service.dart';
|
||||||
|
|
||||||
class DatabaseViewBackendService {
|
class DatabaseViewBackendService {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
DatabaseViewBackendService({
|
DatabaseViewBackendService({
|
||||||
@ -25,6 +27,17 @@ class DatabaseViewBackendService {
|
|||||||
.then((value) => value.leftMap((l) => l.value));
|
.then((value) => value.leftMap((l) => l.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<Either<ViewPB, FlowyError>> updateLayout({
|
||||||
|
required String viewId,
|
||||||
|
required DatabaseLayoutPB layout,
|
||||||
|
}) {
|
||||||
|
final payload = UpdateViewPayloadPB.create()
|
||||||
|
..viewId = viewId
|
||||||
|
..layout = viewLayoutFromDatabaseLayout(layout);
|
||||||
|
|
||||||
|
return FolderEventUpdateView(payload).send();
|
||||||
|
}
|
||||||
|
|
||||||
Future<Either<DatabasePB, FlowyError>> openDatabase() async {
|
Future<Either<DatabasePB, FlowyError>> openDatabase() async {
|
||||||
final payload = DatabaseViewIdPB(value: viewId);
|
final payload = DatabaseViewIdPB(value: viewId);
|
||||||
return DatabaseEventGetDatabase(payload).send();
|
return DatabaseEventGetDatabase(payload).send();
|
||||||
|
@ -2,25 +2,23 @@ import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.da
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
import 'layout_service.dart';
|
import '../database_view_service.dart';
|
||||||
|
|
||||||
part 'layout_bloc.freezed.dart';
|
part 'layout_bloc.freezed.dart';
|
||||||
|
|
||||||
class DatabaseLayoutBloc
|
class DatabaseLayoutBloc
|
||||||
extends Bloc<DatabaseLayoutEvent, DatabaseLayoutState> {
|
extends Bloc<DatabaseLayoutEvent, DatabaseLayoutState> {
|
||||||
final DatabaseLayoutBackendService layoutService;
|
|
||||||
|
|
||||||
DatabaseLayoutBloc({
|
DatabaseLayoutBloc({
|
||||||
required String viewId,
|
required String viewId,
|
||||||
required DatabaseLayoutPB databaseLayout,
|
required DatabaseLayoutPB databaseLayout,
|
||||||
}) : layoutService = DatabaseLayoutBackendService(viewId),
|
}) : super(DatabaseLayoutState.initial(viewId, databaseLayout)) {
|
||||||
super(DatabaseLayoutState.initial(viewId, databaseLayout)) {
|
|
||||||
on<DatabaseLayoutEvent>(
|
on<DatabaseLayoutEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
event.when(
|
event.when(
|
||||||
initial: () {},
|
initial: () {},
|
||||||
updateLayout: (DatabaseLayoutPB layout) {
|
updateLayout: (DatabaseLayoutPB layout) {
|
||||||
layoutService.updateLayout(
|
DatabaseViewBackendService.updateLayout(
|
||||||
fieldId: viewId,
|
viewId: viewId,
|
||||||
layout: layout,
|
layout: layout,
|
||||||
);
|
);
|
||||||
emit(state.copyWith(databaseLayout: layout));
|
emit(state.copyWith(databaseLayout: layout));
|
||||||
|
@ -1,25 +1,5 @@
|
|||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
|
||||||
|
|
||||||
class DatabaseLayoutBackendService {
|
|
||||||
final String viewId;
|
|
||||||
|
|
||||||
DatabaseLayoutBackendService(this.viewId);
|
|
||||||
|
|
||||||
Future<Either<ViewPB, FlowyError>> updateLayout({
|
|
||||||
required String fieldId,
|
|
||||||
required DatabaseLayoutPB layout,
|
|
||||||
}) {
|
|
||||||
final payload = UpdateViewPayloadPB.create()
|
|
||||||
..viewId = viewId
|
|
||||||
..layout = viewLayoutFromDatabaseLayout(layout);
|
|
||||||
|
|
||||||
return FolderEventUpdateView(payload).send();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewLayoutPB viewLayoutFromDatabaseLayout(DatabaseLayoutPB databaseLayout) {
|
ViewLayoutPB viewLayoutFromDatabaseLayout(DatabaseLayoutPB databaseLayout) {
|
||||||
switch (databaseLayout) {
|
switch (databaseLayout) {
|
||||||
|
@ -189,7 +189,7 @@ class _CalendarPageState extends State<CalendarPage> {
|
|||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
// must specify MonthView width for useAvailableVerticalSpace to work properly
|
// must specify MonthView width for useAvailableVerticalSpace to work properly
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
Widget calendar = Padding(
|
return Padding(
|
||||||
padding: PlatformExtension.isMobile
|
padding: PlatformExtension.isMobile
|
||||||
? CalendarSize.contentInsetsMobile
|
? CalendarSize.contentInsetsMobile
|
||||||
: CalendarSize.contentInsets,
|
: CalendarSize.contentInsets,
|
||||||
@ -210,20 +210,6 @@ class _CalendarPageState extends State<CalendarPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (PlatformExtension.isMobile) {
|
|
||||||
calendar = Column(
|
|
||||||
children: [
|
|
||||||
Divider(
|
|
||||||
height: 1,
|
|
||||||
thickness: 1,
|
|
||||||
indent: GridSize.leadingHeaderPadding / 2,
|
|
||||||
endIndent: GridSize.leadingHeaderPadding / 2,
|
|
||||||
),
|
|
||||||
Expanded(child: calendar),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return calendar;
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,9 @@ class CalendarSize {
|
|||||||
|
|
||||||
static EdgeInsets get contentInsetsMobile => EdgeInsets.fromLTRB(
|
static EdgeInsets get contentInsetsMobile => EdgeInsets.fromLTRB(
|
||||||
GridSize.leadingHeaderPadding / 2,
|
GridSize.leadingHeaderPadding / 2,
|
||||||
CalendarSize.headerContainerPadding / 2,
|
0,
|
||||||
GridSize.leadingHeaderPadding / 2,
|
GridSize.leadingHeaderPadding / 2,
|
||||||
CalendarSize.headerContainerPadding / 2,
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
static double get scrollBarSize => 8 * scale;
|
static double get scrollBarSize => 8 * scale;
|
||||||
|
@ -38,8 +38,10 @@ class GridSize {
|
|||||||
static EdgeInsets get footerContentInsets => EdgeInsets.fromLTRB(
|
static EdgeInsets get footerContentInsets => EdgeInsets.fromLTRB(
|
||||||
GridSize.leadingHeaderPadding,
|
GridSize.leadingHeaderPadding,
|
||||||
GridSize.headerContainerPadding,
|
GridSize.headerContainerPadding,
|
||||||
GridSize.headerContainerPadding,
|
PlatformExtension.isMobile
|
||||||
GridSize.headerContainerPadding,
|
? GridSize.leadingHeaderPadding
|
||||||
|
: GridSize.headerContainerPadding,
|
||||||
|
PlatformExtension.isMobile ? 100 : GridSize.headerContainerPadding,
|
||||||
);
|
);
|
||||||
|
|
||||||
static EdgeInsets get contentInsets => EdgeInsets.fromLTRB(
|
static EdgeInsets get contentInsets => EdgeInsets.fromLTRB(
|
||||||
|
@ -2,25 +2,24 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart';
|
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/shortcuts.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/shortcuts.dart';
|
||||||
import 'package:appflowy/plugins/database_view/tab_bar/tab_bar_view.dart';
|
import 'package:appflowy/plugins/database_view/tab_bar/tab_bar_view.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/setting/mobile_database_settings_button.dart';
|
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart';
|
|
||||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
|
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
|
||||||
|
|
||||||
import 'grid_page.dart';
|
|
||||||
import 'grid_scroll.dart';
|
import 'grid_scroll.dart';
|
||||||
import 'layout/sizes.dart';
|
import 'layout/sizes.dart';
|
||||||
import 'widgets/header/mobile_grid_header.dart';
|
import 'widgets/header/mobile_grid_header.dart';
|
||||||
@ -28,8 +27,6 @@ import 'widgets/mobile_fab.dart';
|
|||||||
import 'widgets/row/mobile_row.dart';
|
import 'widgets/row/mobile_row.dart';
|
||||||
|
|
||||||
class MobileGridTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
|
class MobileGridTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
|
||||||
final _toggleExtension = ToggleExtensionNotifier();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget content(
|
Widget content(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
@ -45,21 +42,15 @@ class MobileGridTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget settingBar(BuildContext context, DatabaseController controller) {
|
Widget settingBar(BuildContext context, DatabaseController controller) =>
|
||||||
return MobileDatabaseSettingsButton(
|
const SizedBox.shrink();
|
||||||
key: _makeValueKey(controller),
|
|
||||||
controller: controller,
|
|
||||||
toggleExtension: _toggleExtension,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget settingBarExtension(
|
Widget settingBarExtension(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
DatabaseController controller,
|
DatabaseController controller,
|
||||||
) {
|
) =>
|
||||||
return const SizedBox.shrink();
|
const SizedBox.shrink();
|
||||||
}
|
|
||||||
|
|
||||||
ValueKey _makeValueKey(DatabaseController controller) {
|
ValueKey _makeValueKey(DatabaseController controller) {
|
||||||
return ValueKey(controller.viewId);
|
return ValueKey(controller.viewId);
|
||||||
@ -217,8 +208,7 @@ class _GridRows extends StatelessWidget {
|
|||||||
return BlocBuilder<GridBloc, GridState>(
|
return BlocBuilder<GridBloc, GridState>(
|
||||||
buildWhen: (previous, current) => previous.fields != current.fields,
|
buildWhen: (previous, current) => previous.fields != current.fields,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final double contentWidth =
|
final double contentWidth = _getContentWidth(state.fields);
|
||||||
(state.fields.length + 1) * 200 + GridSize.leadingHeaderPadding;
|
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: _WrapScrollView(
|
child: _WrapScrollView(
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
@ -248,6 +238,14 @@ class _GridRows extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double _getContentWidth(List<FieldInfo> fields) {
|
||||||
|
final visibleFields = fields.where(
|
||||||
|
(field) =>
|
||||||
|
field.fieldSettings?.visibility != FieldVisibility.AlwaysHidden,
|
||||||
|
);
|
||||||
|
return (visibleFields.length + 1) * 200 + GridSize.leadingHeaderPadding * 2;
|
||||||
|
}
|
||||||
|
|
||||||
Widget _renderList(
|
Widget _renderList(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
GridState state,
|
GridState state,
|
||||||
@ -281,20 +279,9 @@ class _GridRows extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
itemCount: state.rowInfos.length,
|
itemCount: state.rowInfos.length,
|
||||||
itemBuilder: (context, index) => children[index],
|
itemBuilder: (context, index) => children[index],
|
||||||
footer: Column(
|
footer: Padding(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
padding: GridSize.footerContentInsets,
|
||||||
children: [
|
child: _AddRowButton(),
|
||||||
Padding(
|
|
||||||
padding: GridSize.footerContentInsets,
|
|
||||||
child: _AddRowButton(),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 30,
|
|
||||||
alignment: AlignmentDirectional.centerStart,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
|
||||||
child: const _GridFooter(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -360,56 +347,17 @@ class _WrapScrollView extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ScrollbarListStack(
|
return SingleChildScrollView(
|
||||||
axis: Axis.vertical,
|
controller: scrollController.horizontalController,
|
||||||
controller: scrollController.verticalController,
|
scrollDirection: Axis.horizontal,
|
||||||
barSize: GridSize.scrollBarSize,
|
child: SizedBox(
|
||||||
autoHideScrollbar: false,
|
width: contentWidth,
|
||||||
child: StyledSingleChildScrollView(
|
child: child,
|
||||||
autoHideScrollbar: false,
|
|
||||||
controller: scrollController.horizontalController,
|
|
||||||
axis: Axis.horizontal,
|
|
||||||
child: SizedBox(
|
|
||||||
width: contentWidth,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridFooter extends StatelessWidget {
|
|
||||||
const _GridFooter();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocSelector<GridBloc, GridState, int>(
|
|
||||||
selector: (state) => state.rowCount,
|
|
||||||
builder: (context, rowCount) {
|
|
||||||
return Padding(
|
|
||||||
padding: GridSize.contentInsets,
|
|
||||||
child: RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
text: "${LocaleKeys.grid_row_count.tr()} :",
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
|
||||||
color: Theme.of(context).hintColor,
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: ' $rowCount',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
|
||||||
color: AFThemeExtension.of(context).gridRowCountColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AddRowButton extends StatelessWidget {
|
class _AddRowButton extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/database/field/bottom_sheet_create_field.dart';
|
import 'package:appflowy/mobile/presentation/database/field/mobile_field_bottom_sheets.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/database/field/bottom_sheet_create_field.dart';
|
import 'package:appflowy/mobile/presentation/database/field/mobile_field_bottom_sheets.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
|
||||||
|
@ -37,12 +37,8 @@ class TabBarHeader extends StatelessWidget {
|
|||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
BlocBuilder<DatabaseTabBarBloc, DatabaseTabBarState>(
|
const Flexible(
|
||||||
builder: (context, state) {
|
child: DatabaseTabBar(),
|
||||||
return const Flexible(
|
|
||||||
child: DatabaseTabBar(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
BlocBuilder<DatabaseTabBarBloc, DatabaseTabBarState>(
|
BlocBuilder<DatabaseTabBarBloc, DatabaseTabBarState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
|
|
||||||
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
|
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
|
||||||
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/application/tab_bar_bloc.dart';
|
import 'package:appflowy/plugins/database_view/application/tab_bar_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
import 'package:appflowy/plugins/database_view/widgets/setting/mobile_database_controls.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/setting/mobile_database_settings_button.dart';
|
|
||||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
|
||||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
|
import '../../grid/presentation/grid_page.dart';
|
||||||
|
|
||||||
class MobileTabBarHeader extends StatefulWidget {
|
class MobileTabBarHeader extends StatefulWidget {
|
||||||
const MobileTabBarHeader({super.key});
|
const MobileTabBarHeader({super.key});
|
||||||
@ -22,13 +18,40 @@ class MobileTabBarHeader extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MobileTabBarHeaderState extends State<MobileTabBarHeader> {
|
class _MobileTabBarHeaderState extends State<MobileTabBarHeader> {
|
||||||
final controller = TextEditingController();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
Widget build(BuildContext context) {
|
||||||
controller.dispose();
|
return Padding(
|
||||||
super.dispose();
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Expanded(child: _DatabaseViewList()),
|
||||||
|
const HSpace(10),
|
||||||
|
BlocBuilder<DatabaseTabBarBloc, DatabaseTabBarState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final currentView = state.tabBars.firstWhereIndexedOrNull(
|
||||||
|
(index, tabBar) => index == state.selectedIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (currentView == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return MobileDatabaseControls(
|
||||||
|
controller: state
|
||||||
|
.tabBarControllerByViewId[currentView.viewId]!.controller,
|
||||||
|
toggleExtension: ToggleExtensionNotifier(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DatabaseViewList extends StatelessWidget {
|
||||||
|
const _DatabaseViewList();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -42,76 +65,127 @@ class _MobileTabBarHeaderState extends State<MobileTabBarHeader> {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.text = currentView.view.name;
|
final children = state.tabBars.mapIndexed((index, tabBar) {
|
||||||
|
return Padding(
|
||||||
return Padding(
|
padding: EdgeInsetsDirectional.only(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
start: index == 0 ? 0 : 2,
|
||||||
child: Row(
|
end: 2,
|
||||||
children: [
|
),
|
||||||
_buildViewIconButton(currentView.view),
|
child: _DatabaseViewListItem(
|
||||||
const HSpace(8.0),
|
tabBar: tabBar,
|
||||||
Expanded(
|
isSelected: currentView.viewId == tabBar.viewId,
|
||||||
child: FlowyTextField(
|
),
|
||||||
autoFocus: false,
|
|
||||||
maxLines: 1,
|
|
||||||
controller: controller,
|
|
||||||
textAlignVertical: TextAlignVertical.top,
|
|
||||||
textInputAction: TextInputAction.done,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
border: InputBorder.none,
|
|
||||||
enabledBorder: InputBorder.none,
|
|
||||||
focusedBorder: InputBorder.none,
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
textStyle: Theme.of(context).textTheme.titleLarge,
|
|
||||||
onSubmitted: (value) {
|
|
||||||
if (value.isNotEmpty) {
|
|
||||||
context.read<ViewBloc>().add(
|
|
||||||
ViewEvent.rename(value),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onCanceled: () {
|
|
||||||
controller.text = currentView.view.name;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
MobileDatabaseSettingsButton(
|
|
||||||
controller: state
|
|
||||||
.tabBarControllerByViewId[currentView.viewId]!.controller,
|
|
||||||
toggleExtension: ToggleExtensionNotifier(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildViewIconButton(ViewPB view) {
|
|
||||||
final icon = view.icon.value.isNotEmpty
|
|
||||||
? EmojiText(
|
|
||||||
emoji: view.icon.value,
|
|
||||||
fontSize: 24.0,
|
|
||||||
)
|
|
||||||
: SizedBox.square(
|
|
||||||
dimension: 26.0,
|
|
||||||
child: view.defaultIcon(),
|
|
||||||
);
|
);
|
||||||
return FlowyButton(
|
}).toList();
|
||||||
text: icon,
|
|
||||||
useIntrinsicWidth: true,
|
return SingleChildScrollView(
|
||||||
onTap: () async {
|
scrollDirection: Axis.horizontal,
|
||||||
final result = await context.push<EmojiPickerResult>(
|
child: Row(children: children),
|
||||||
MobileEmojiPickerScreen.routeName,
|
|
||||||
);
|
);
|
||||||
if (context.mounted && result != null) {
|
|
||||||
await ViewBackendService.updateViewIcon(
|
|
||||||
viewId: view.id,
|
|
||||||
viewIcon: result.emoji,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _DatabaseViewListItem extends StatefulWidget {
|
||||||
|
const _DatabaseViewListItem({
|
||||||
|
required this.tabBar,
|
||||||
|
required this.isSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DatabaseTabBar tabBar;
|
||||||
|
final bool isSelected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_DatabaseViewListItem> createState() => _DatabaseViewListItemState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DatabaseViewListItemState extends State<_DatabaseViewListItem> {
|
||||||
|
late final MaterialStatesController statesController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
statesController = MaterialStatesController(
|
||||||
|
<MaterialState>{if (widget.isSelected) MaterialState.selected},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (widget.isSelected != oldWidget.isSelected) {
|
||||||
|
statesController.update(MaterialState.selected, widget.isSelected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TextButton(
|
||||||
|
statesController: statesController,
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: const MaterialStatePropertyAll(
|
||||||
|
EdgeInsets.symmetric(horizontal: 12, vertical: 7),
|
||||||
|
),
|
||||||
|
maximumSize: MaterialStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return const Size(150, 48);
|
||||||
|
}
|
||||||
|
return const Size(120, 48);
|
||||||
|
}),
|
||||||
|
minimumSize: const MaterialStatePropertyAll(Size(48, 0)),
|
||||||
|
shape: const MaterialStatePropertyAll(
|
||||||
|
RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return const Color(0x0F212729);
|
||||||
|
}
|
||||||
|
return Colors.transparent;
|
||||||
|
}),
|
||||||
|
overlayColor: MaterialStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return Colors.transparent;
|
||||||
|
}
|
||||||
|
return Theme.of(context).colorScheme.secondary;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_buildViewIconButton(context, widget.tabBar.view),
|
||||||
|
const HSpace(6),
|
||||||
|
Flexible(
|
||||||
|
child: FlowyText(
|
||||||
|
widget.tabBar.view.name,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: widget.isSelected ? FontWeight.w500 : FontWeight.w400,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
if (!widget.isSelected) {
|
||||||
|
context
|
||||||
|
.read<DatabaseTabBarBloc>()
|
||||||
|
.add(DatabaseTabBarEvent.selectView(widget.tabBar.viewId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildViewIconButton(BuildContext context, ViewPB view) {
|
||||||
|
return view.icon.value.isNotEmpty
|
||||||
|
? EmojiText(
|
||||||
|
emoji: view.icon.value,
|
||||||
|
fontSize: 16.0,
|
||||||
|
)
|
||||||
|
: SizedBox.square(
|
||||||
|
dimension: 16.0,
|
||||||
|
child: view.defaultIcon(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/tab_bar_bloc.dart';
|
import 'package:appflowy/plugins/database_view/application/tab_bar_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||||
import 'package:appflowy/plugins/database_view/tab_bar/mobile/mobile_tab_bar_header.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/widgets/share_button.dart';
|
import 'package:appflowy/plugins/database_view/widgets/share_button.dart';
|
||||||
import 'package:appflowy/plugins/util.dart';
|
import 'package:appflowy/plugins/util.dart';
|
||||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||||
@ -15,6 +14,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import 'desktop/tab_bar_header.dart';
|
import 'desktop/tab_bar_header.dart';
|
||||||
|
import 'mobile/mobile_tab_bar_header.dart';
|
||||||
|
|
||||||
abstract class DatabaseTabBarItemBuilder {
|
abstract class DatabaseTabBarItemBuilder {
|
||||||
const DatabaseTabBarItemBuilder();
|
const DatabaseTabBarItemBuilder();
|
||||||
@ -102,14 +102,22 @@ class _DatabaseTabBarViewState extends State<DatabaseTabBarView> {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Padding(
|
if (PlatformExtension.isDesktop) {
|
||||||
padding: EdgeInsets.symmetric(
|
return Padding(
|
||||||
horizontal: GridSize.leadingHeaderPadding,
|
padding: EdgeInsets.symmetric(
|
||||||
),
|
horizontal: GridSize.leadingHeaderPadding,
|
||||||
child: PlatformExtension.isMobile
|
),
|
||||||
? const MobileTabBarHeader()
|
child: const TabBarHeader(),
|
||||||
: const TabBarHeader(),
|
);
|
||||||
);
|
} else {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: GridSize.leadingHeaderPadding,
|
||||||
|
right: 8,
|
||||||
|
),
|
||||||
|
child: const MobileTabBarHeader(),
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.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/mobile/presentation/widgets/flowy_paginated_bottom_sheet.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/filter/filter_menu_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/filter/filter_menu_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/sort/sort_menu_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/sort/sort_menu_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/setting/database_settings_list.dart';
|
import 'package: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:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class MobileDatabaseSettingsButton extends StatelessWidget {
|
class MobileDatabaseControls extends StatelessWidget {
|
||||||
const MobileDatabaseSettingsButton({
|
const MobileDatabaseControls({
|
||||||
super.key,
|
super.key,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required this.toggleExtension,
|
required this.toggleExtension,
|
||||||
@ -55,17 +58,26 @@ class MobileDatabaseSettingsButton extends StatelessWidget {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
return SizedBox(
|
return Row(
|
||||||
height: 24,
|
children: [
|
||||||
width: 24,
|
_DatabaseControlButton(
|
||||||
child: IconButton(
|
icon: FlowySvgs.settings_s,
|
||||||
padding: EdgeInsets.zero,
|
onTap: () {
|
||||||
onPressed: () => _showMobileSettings(context, controller),
|
showMobileBottomSheet(
|
||||||
icon: const FlowySvg(
|
context,
|
||||||
FlowySvgs.m_setting_m,
|
padding: EdgeInsets.zero,
|
||||||
size: Size.square(24),
|
builder: (_) => MobileEditDatabaseViewScreen(
|
||||||
|
databaseController: controller,
|
||||||
|
viewPB: context.read<ViewBloc>().state.view,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
_DatabaseControlButton(
|
||||||
|
icon: FlowySvgs.align_left_s,
|
||||||
|
onTap: () => _showMobileSettings(context, controller),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -87,3 +99,30 @@ class MobileDatabaseSettingsButton extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _DatabaseControlButton extends StatelessWidget {
|
||||||
|
const _DatabaseControlButton({
|
||||||
|
required this.onTap,
|
||||||
|
required this.icon,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final FlowySvgData icon;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 36,
|
||||||
|
width: 36,
|
||||||
|
child: IconButton(
|
||||||
|
splashRadius: 18,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: onTap,
|
||||||
|
icon: FlowySvg(
|
||||||
|
icon,
|
||||||
|
size: const Size.square(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/database/field/bottom_sheet_create_field.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
|
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
|
||||||
@ -9,10 +8,7 @@ import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.da
|
|||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/setting/field_visibility_extension.dart';
|
import 'package:appflowy/plugins/database_view/widgets/setting/field_visibility_extension.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
@ -63,20 +59,10 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
|
|||||||
fieldInfo: field,
|
fieldInfo: field,
|
||||||
popoverMutex: _popoverMutex,
|
popoverMutex: _popoverMutex,
|
||||||
index: index,
|
index: index,
|
||||||
bloc: _bloc,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
if (PlatformExtension.isMobile) {
|
|
||||||
return ListView.separated(
|
|
||||||
shrinkWrap: true,
|
|
||||||
itemCount: cells.length,
|
|
||||||
itemBuilder: (_, index) => cells[index],
|
|
||||||
separatorBuilder: (_, __) => const VSpace(8),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ReorderableListView(
|
return ReorderableListView(
|
||||||
proxyDecorator: (child, index, _) => Material(
|
proxyDecorator: (child, index, _) => Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
@ -110,7 +96,7 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
class DatabasePropertyCell extends StatelessWidget {
|
class DatabasePropertyCell extends StatefulWidget {
|
||||||
const DatabasePropertyCell({
|
const DatabasePropertyCell({
|
||||||
super.key,
|
super.key,
|
||||||
required this.fieldInfo,
|
required this.fieldInfo,
|
||||||
@ -118,7 +104,6 @@ class DatabasePropertyCell extends StatelessWidget {
|
|||||||
required this.popoverMutex,
|
required this.popoverMutex,
|
||||||
required this.index,
|
required this.index,
|
||||||
required this.fieldController,
|
required this.fieldController,
|
||||||
required this.bloc,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final FieldInfo fieldInfo;
|
final FieldInfo fieldInfo;
|
||||||
@ -126,126 +111,12 @@ class DatabasePropertyCell extends StatelessWidget {
|
|||||||
final PopoverMutex popoverMutex;
|
final PopoverMutex popoverMutex;
|
||||||
final int index;
|
final int index;
|
||||||
final FieldController fieldController;
|
final FieldController fieldController;
|
||||||
final DatabasePropertyBloc bloc;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
State<DatabasePropertyCell> createState() => _DatabasePropertyCellState();
|
||||||
return PlatformExtension.isMobile
|
|
||||||
? MobileDatabasePropertyCell(
|
|
||||||
fieldInfo: fieldInfo,
|
|
||||||
viewId: viewId,
|
|
||||||
fieldController: fieldController,
|
|
||||||
bloc: bloc,
|
|
||||||
)
|
|
||||||
: DesktopDatabasePropertyCell(
|
|
||||||
fieldInfo: fieldInfo,
|
|
||||||
viewId: viewId,
|
|
||||||
popoverMutex: popoverMutex,
|
|
||||||
index: index,
|
|
||||||
fieldController: fieldController,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MobileDatabasePropertyCell extends StatefulWidget {
|
class _DatabasePropertyCellState extends State<DatabasePropertyCell> {
|
||||||
const MobileDatabasePropertyCell({
|
|
||||||
super.key,
|
|
||||||
required this.fieldInfo,
|
|
||||||
required this.viewId,
|
|
||||||
required this.fieldController,
|
|
||||||
required this.bloc,
|
|
||||||
});
|
|
||||||
|
|
||||||
final FieldInfo fieldInfo;
|
|
||||||
final String viewId;
|
|
||||||
final FieldController fieldController;
|
|
||||||
final DatabasePropertyBloc bloc;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<MobileDatabasePropertyCell> createState() =>
|
|
||||||
_MobileDatabasePropertyCellState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MobileDatabasePropertyCellState
|
|
||||||
extends State<MobileDatabasePropertyCell> {
|
|
||||||
late bool isVisible = widget.fieldInfo.visibility?.isVisibleState() ?? false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(color: Theme.of(context).dividerColor),
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
),
|
|
||||||
child: InkWell(
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
onTap: () =>
|
|
||||||
showEditFieldScreen(context, widget.viewId, widget.fieldInfo),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
FlowySvg(
|
|
||||||
widget.fieldInfo.fieldType.icon(),
|
|
||||||
color: Theme.of(context).iconTheme.color,
|
|
||||||
size: const Size.square(24),
|
|
||||||
),
|
|
||||||
const HSpace(8),
|
|
||||||
FlowyText.medium(
|
|
||||||
widget.fieldInfo.name,
|
|
||||||
color: AFThemeExtension.of(context).textColor,
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
// Toggle Visibility
|
|
||||||
Toggle(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
value: isVisible,
|
|
||||||
style: ToggleStyle.mobile,
|
|
||||||
onChanged: (newValue) {
|
|
||||||
final newVisibility = widget.fieldInfo.visibility!.toggle();
|
|
||||||
|
|
||||||
context.read<DatabasePropertyBloc>().add(
|
|
||||||
DatabasePropertyEvent.setFieldVisibility(
|
|
||||||
widget.fieldInfo.id,
|
|
||||||
newVisibility,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
setState(() => isVisible = !newValue);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@visibleForTesting
|
|
||||||
class DesktopDatabasePropertyCell extends StatefulWidget {
|
|
||||||
const DesktopDatabasePropertyCell({
|
|
||||||
super.key,
|
|
||||||
required this.fieldController,
|
|
||||||
required this.fieldInfo,
|
|
||||||
required this.viewId,
|
|
||||||
required this.popoverMutex,
|
|
||||||
required this.index,
|
|
||||||
});
|
|
||||||
|
|
||||||
final FieldController fieldController;
|
|
||||||
final FieldInfo fieldInfo;
|
|
||||||
final String viewId;
|
|
||||||
final PopoverMutex popoverMutex;
|
|
||||||
final int index;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<DesktopDatabasePropertyCell> createState() =>
|
|
||||||
_DesktopDatabasePropertyCellState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DesktopDatabasePropertyCellState
|
|
||||||
extends State<DesktopDatabasePropertyCell> {
|
|
||||||
final PopoverController _popoverController = PopoverController();
|
final PopoverController _popoverController = PopoverController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
3
frontend/resources/flowy_icons/16x/card_view.svg
Normal file
3
frontend/resources/flowy_icons/16x/card_view.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4.46667 8.08256H4.4159V8.13333V11.0667V11.1174H4.46667H14.7333H14.7841V11.0667V8.13333V8.08256H14.7333H4.46667ZM3.05077 3.73333C3.05077 3.35636 3.35636 3.05077 3.73333 3.05077H15.4667C15.8436 3.05077 16.1492 3.35636 16.1492 3.73333C16.1492 4.1103 15.8436 4.4159 15.4667 4.4159H3.73333C3.35636 4.4159 3.05077 4.1103 3.05077 3.73333ZM3.05077 15.4667C3.05077 15.0897 3.35636 14.7841 3.73333 14.7841H15.4667C15.8436 14.7841 16.1492 15.0897 16.1492 15.4667C16.1492 15.8436 15.8436 16.1492 15.4667 16.1492H3.73333C3.35636 16.1492 3.05077 15.8436 3.05077 15.4667ZM3.05077 8.13333C3.05077 7.35135 3.68469 6.71744 4.46667 6.71744H14.7333C15.5153 6.71744 16.1492 7.35135 16.1492 8.13333V11.0667C16.1492 11.8486 15.5153 12.4826 14.7333 12.4826H4.46667C3.68469 12.4826 3.05077 11.8486 3.05077 11.0667V8.13333Z" fill="#2B2F36" stroke="white" stroke-width="0.101538"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 968 B |
8
frontend/resources/flowy_icons/16x/disorder_list.svg
Normal file
8
frontend/resources/flowy_icons/16x/disorder_list.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3.97143 5.94286C4.50793 5.94286 4.94286 5.50793 4.94286 4.97143C4.94286 4.43492 4.50793 4 3.97143 4C3.43492 4 3 4.43492 3 4.97143C3 5.50793 3.43492 5.94286 3.97143 5.94286Z" fill="#2B2F36"/>
|
||||||
|
<path d="M7.53333 4.32381C7.17566 4.32381 6.88571 4.61376 6.88571 4.97143C6.88571 5.3291 7.17566 5.61905 7.53333 5.61905H15.9524C16.3101 5.61905 16.6 5.3291 16.6 4.97143C16.6 4.61376 16.3101 4.32381 15.9524 4.32381H7.53333Z" fill="#2B2F36"/>
|
||||||
|
<path d="M7.53333 9.50476C7.17566 9.50476 6.88571 9.79471 6.88571 10.1524C6.88571 10.5101 7.17566 10.8 7.53333 10.8H15.9524C16.3101 10.8 16.6 10.5101 16.6 10.1524C16.6 9.79471 16.3101 9.50476 15.9524 9.50476H7.53333Z" fill="#2B2F36"/>
|
||||||
|
<path d="M6.88571 15.3333C6.88571 14.9757 7.17566 14.6857 7.53333 14.6857H15.9524C16.3101 14.6857 16.6 14.9757 16.6 15.3333C16.6 15.691 16.3101 15.981 15.9524 15.981H7.53333C7.17566 15.981 6.88571 15.691 6.88571 15.3333Z" fill="#2B2F36"/>
|
||||||
|
<path d="M4.94286 10.1524C4.94286 10.6889 4.50793 11.1238 3.97143 11.1238C3.43492 11.1238 3 10.6889 3 10.1524C3 9.61588 3.43492 9.18095 3.97143 9.18095C4.50793 9.18095 4.94286 9.61588 4.94286 10.1524Z" fill="#2B2F36"/>
|
||||||
|
<path d="M3.97143 16.3048C4.50793 16.3048 4.94286 15.8698 4.94286 15.3333C4.94286 14.7968 4.50793 14.3619 3.97143 14.3619C3.43492 14.3619 3 14.7968 3 15.3333C3 15.8698 3.43492 16.3048 3.97143 16.3048Z" fill="#2B2F36"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
@ -452,7 +452,14 @@
|
|||||||
"filterBy": "Filter by...",
|
"filterBy": "Filter by...",
|
||||||
"typeAValue": "Type a value...",
|
"typeAValue": "Type a value...",
|
||||||
"layout": "Layout",
|
"layout": "Layout",
|
||||||
"databaseLayout": "Layout"
|
"databaseLayout": "Layout",
|
||||||
|
"editView": "Edit View",
|
||||||
|
"boardSettings": "Board settings",
|
||||||
|
"calendarSettings": "Calendar settings",
|
||||||
|
"createView": "New view",
|
||||||
|
"duplicateView": "Duplicate view",
|
||||||
|
"deleteView": "Delete view",
|
||||||
|
"numberOfVisibleFields": "{} shown"
|
||||||
},
|
},
|
||||||
"textFilter": {
|
"textFilter": {
|
||||||
"contains": "Contains",
|
"contains": "Contains",
|
||||||
@ -1162,4 +1169,4 @@
|
|||||||
"addField": "Add field",
|
"addField": "Add field",
|
||||||
"userIcon": "User icon"
|
"userIcon": "User icon"
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user