chore: revamp database setting action sheets (#4570)

* chore: revamp database setting action sheets

* chore: field picker improvement

* chore: fix comments
This commit is contained in:
Richard Shiue 2024-02-01 15:41:02 +08:00 committed by GitHub
parent 9d28360887
commit 20391bfec4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 339 additions and 532 deletions

View File

@ -85,6 +85,39 @@ class AppBarDoneButton extends StatelessWidget {
}
}
class AppBarFilledDoneButton extends StatelessWidget {
const AppBarFilledDoneButton({super.key, required this.onTap});
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(8, 4, 8, 8),
child: TextButton(
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 0,
visualDensity: VisualDensity.compact,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
enableFeedback: true,
backgroundColor: Theme.of(context).primaryColor,
),
onPressed: onTap,
child: FlowyText.medium(
LocaleKeys.button_done.tr(),
fontSize: 16,
color: Theme.of(context).colorScheme.onPrimary,
overflow: TextOverflow.ellipsis,
),
),
);
}
}
class AppBarMoreButton extends StatelessWidget {
const AppBarMoreButton({
super.key,

View File

@ -4,7 +4,6 @@ export 'bottom_sheet_drag_handler.dart';
export 'bottom_sheet_rename_widget.dart';
export 'bottom_sheet_view_item.dart';
export 'bottom_sheet_view_item_body.dart';
export 'bottom_sheet_view_item_header.dart';
export 'bottom_sheet_view_page.dart';
export 'default_mobile_action_pane.dart';
export 'show_mobile_bottom_sheet.dart';

View File

@ -1,59 +0,0 @@
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class MobileViewItemBottomSheetHeader extends StatelessWidget {
const MobileViewItemBottomSheetHeader({
super.key,
required this.view,
required this.showBackButton,
required this.onBack,
});
final ViewPB view;
final bool showBackButton;
final VoidCallback onBack;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// back button,
showBackButton
? InkWell(
onTap: onBack,
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Icon(
Icons.arrow_back_ios_new_rounded,
size: 24.0,
),
),
)
: FlowyButton(
useIntrinsicWidth: true,
text: const Icon(
Icons.close,
),
margin: EdgeInsets.zero,
onTap: () {
context.pop();
},
),
// title
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.6,
),
child: FlowyText.medium(
view.name,
overflow: TextOverflow.ellipsis,
),
),
const HSpace(24.0),
],
);
}
}

View File

@ -7,10 +7,12 @@ import 'package:flutter/material.dart';
Future<T?> showMobileBottomSheet<T>(
BuildContext context, {
required WidgetBuilder builder,
bool useSafeArea = true,
bool isDragEnabled = true,
bool showDragHandle = false,
bool showHeader = false,
// this field is only used if showHeader is true
bool showBackButton = false,
bool showCloseButton = false,
// this field is only used if showHeader is true
String title = '',
@ -20,7 +22,7 @@ Future<T?> showMobileBottomSheet<T>(
bool useRootNavigator = false,
ShapeBorder? shape,
// the padding of the content, the padding of the header area is fixed
EdgeInsets padding = const EdgeInsets.all(0.0),
EdgeInsets padding = EdgeInsets.zero,
Color? backgroundColor,
BoxConstraints? constraints,
Color? barrierColor,
@ -32,10 +34,11 @@ Future<T?> showMobileBottomSheet<T>(
double maxChildSize = 0.8,
double initialChildSize = 0.51,
}) async {
assert(() {
if (showCloseButton || title.isNotEmpty) assert(showHeader);
return true;
}());
assert(
showHeader ||
title.isEmpty && !showCloseButton && !showBackButton && !showDoneButton,
);
assert(!(showCloseButton && showBackButton));
shape ??= const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
@ -83,6 +86,7 @@ Future<T?> showMobileBottomSheet<T>(
children.add(
_Header(
showCloseButton: showCloseButton,
showBackButton: showBackButton,
showDoneButton: showDoneButton,
title: title,
),
@ -147,23 +151,30 @@ Future<T?> showMobileBottomSheet<T>(
VSpace(MediaQuery.of(context).padding.bottom == 0 ? 28.0 : 16.0),
);
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: children,
),
);
return useSafeArea
? SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: children,
),
)
: Column(
mainAxisSize: MainAxisSize.min,
children: children,
);
},
);
}
class _Header extends StatelessWidget {
const _Header({
required this.showBackButton,
required this.showCloseButton,
required this.title,
required this.showDoneButton,
});
final bool showBackButton;
final bool showCloseButton;
final String title;
final bool showDoneButton;
@ -176,6 +187,11 @@ class _Header extends StatelessWidget {
height: 44.0, // the height of the header area is fixed
child: Stack(
children: [
if (showBackButton)
const Align(
alignment: Alignment.centerLeft,
child: AppBarBackButton(),
),
if (showCloseButton)
const Align(
alignment: Alignment.centerLeft,

View File

@ -128,14 +128,17 @@ void showQuickEditField(
/// Display a list of fields in the current database that users can choose from.
Future<String?> showFieldPicker(
BuildContext context,
String title,
String? selectedFieldId,
FieldController fieldController,
bool Function(FieldInfo fieldInfo) filterBy,
) {
return showMobileBottomSheet<String>(
context,
showDivider: false,
builder: (context) {
return MobileFieldPickerList(
title: title,
selectedFieldId: selectedFieldId,
fieldController: fieldController,
filterBy: filterBy,

View File

@ -1,11 +1,10 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
import 'package:appflowy/plugins/base/drag_handler.dart';
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
import 'package:appflowy/plugins/database/application/field/field_info.dart';
import 'package:appflowy/util/field_type_extension.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
@ -13,11 +12,13 @@ import 'package:go_router/go_router.dart';
class MobileFieldPickerList extends StatefulWidget {
MobileFieldPickerList({
super.key,
required this.title,
required this.selectedFieldId,
required FieldController fieldController,
required bool Function(FieldInfo fieldInfo) filterBy,
}) : fields = fieldController.fieldInfos.where(filterBy).toList();
final String title;
final String? selectedFieldId;
final List<FieldInfo> fields;
@ -36,87 +37,79 @@ class _MobileFieldPickerListState extends State<MobileFieldPickerList> {
@override
Widget build(BuildContext context) {
return Column(
children: [
const Center(child: DragHandler()),
_Header(newFieldId: newFieldId),
Expanded(
child: ListView.builder(
itemCount: widget.fields.length,
itemBuilder: (context, index) => _FieldButton(
field: widget.fields[index],
showTopBorder: index == 0,
isSelected: widget.fields[index].id == newFieldId,
onSelect: (fieldId) => setState(() => newFieldId = fieldId),
),
),
),
],
);
}
}
class _Header extends StatelessWidget {
const _Header({required this.newFieldId});
final String? newFieldId;
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return DraggableScrollableSheet(
expand: false,
snap: true,
initialChildSize: 0.98,
minChildSize: 0.98,
maxChildSize: 0.98,
builder: (context, scrollController) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox.square(
dimension: 36,
child: IconButton(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
padding: EdgeInsets.zero,
onPressed: () => context.pop(),
icon: const FlowySvg(
FlowySvgs.arrow_left_s,
size: Size.square(20),
),
),
const DragHandler(),
_Header(
title: widget.title,
onDone: (context) => context.pop(newFieldId),
),
Padding(
padding: const EdgeInsets.fromLTRB(8, 4, 8, 8),
child: TextButton(
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 5,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 0,
visualDensity: VisualDensity.compact,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
enableFeedback: true,
backgroundColor: Theme.of(context).primaryColor,
),
onPressed: () => context.pop(newFieldId),
child: FlowyText.medium(
LocaleKeys.button_done.tr(),
fontSize: 16,
color: Theme.of(context).colorScheme.onPrimary,
overflow: TextOverflow.ellipsis,
SingleChildScrollView(
controller: scrollController,
child: ListView.builder(
shrinkWrap: true,
itemCount: widget.fields.length,
itemBuilder: (context, index) => _FieldButton(
field: widget.fields[index],
showTopBorder: index == 0,
isSelected: widget.fields[index].id == newFieldId,
onSelect: (fieldId) => setState(() => newFieldId = fieldId),
),
),
),
],
);
},
);
}
}
/// Same header as the one in showMobileBottomSheet, but allows popping the
/// sheet with a value.
class _Header extends StatelessWidget {
const _Header({
required this.title,
required this.onDone,
});
final String title;
final void Function(BuildContext context) onDone;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 4.0),
child: SizedBox(
height: 44.0,
child: Stack(
children: [
const Align(
alignment: Alignment.centerLeft,
child: AppBarBackButton(),
),
Align(
child: FlowyText.medium(
title,
fontSize: 16.0,
),
),
Align(
alignment: Alignment.centerRight,
child: AppBarDoneButton(
onTap: () => onDone(context),
),
),
],
),
Center(
child: FlowyText.medium(
LocaleKeys.calendar_settings_changeLayoutDateField.tr(),
fontSize: 16,
),
),
],
),
);
}
}

View File

@ -3,7 +3,6 @@ import 'dart:ui';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
import 'package:appflowy/plugins/base/drag_handler.dart';
import 'package:appflowy/plugins/database/application/database_controller.dart';
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
import 'package:appflowy/plugins/database/application/field/field_info.dart';
@ -11,7 +10,6 @@ import 'package:appflowy/plugins/database/application/setting/property_bloc.dart
import 'package:appflowy/plugins/database/widgets/setting/field_visibility_extension.dart';
import 'package:appflowy/util/field_type_extension.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -30,61 +28,9 @@ class MobileDatabaseFieldList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DraggableScrollableSheet(
expand: false,
snap: true,
initialChildSize: 1.0,
minChildSize: 0.0,
builder: (context, controller) {
return Material(
child: Column(
children: [
const Center(child: DragHandler()),
const _MobileDatabaseFieldListHeader(),
Expanded(
child: SingleChildScrollView(
child: _MobileDatabaseFieldListBody(
databaseController: databaseController,
view: context.read<ViewBloc>().state.view,
),
),
),
],
),
);
},
);
}
}
class _MobileDatabaseFieldListHeader extends StatelessWidget {
const _MobileDatabaseFieldListHeader();
@override
Widget build(BuildContext context) {
const iconWidth = 30.0;
return Padding(
padding: const EdgeInsets.fromLTRB(8, 4, 8, 12),
child: Stack(
children: [
Align(
alignment: Alignment.centerLeft,
child: FlowyIconButton(
icon: const FlowySvg(
FlowySvgs.arrow_left_m,
size: Size.square(iconWidth),
),
onPressed: () => Navigator.of(context).maybePop(),
),
),
Align(
child: FlowyText.medium(
LocaleKeys.grid_settings_properties.tr(),
fontSize: 16,
),
),
],
),
return _MobileDatabaseFieldListBody(
databaseController: databaseController,
viewId: context.read<ViewBloc>().state.view.id,
);
}
}
@ -92,17 +38,17 @@ class _MobileDatabaseFieldListHeader extends StatelessWidget {
class _MobileDatabaseFieldListBody extends StatelessWidget {
const _MobileDatabaseFieldListBody({
required this.databaseController,
required this.view,
required this.viewId,
});
final DatabaseController databaseController;
final ViewPB view;
final String viewId;
@override
Widget build(BuildContext context) {
return BlocProvider<DatabasePropertyBloc>(
create: (_) => DatabasePropertyBloc(
viewId: view.id,
viewId: viewId,
fieldController: databaseController.fieldController,
)..add(const DatabasePropertyEvent.initial()),
child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>(
@ -114,7 +60,7 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
final firstField = fields.removeAt(0);
final firstCell = DatabaseFieldListTile(
key: ValueKey(firstField.id),
viewId: view.id,
viewId: viewId,
fieldController: databaseController.fieldController,
fieldInfo: firstField,
showTopBorder: true,
@ -123,7 +69,7 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
.mapIndexed(
(index, field) => DatabaseFieldListTile(
key: ValueKey(field.id),
viewId: view.id,
viewId: viewId,
fieldController: databaseController.fieldController,
fieldInfo: field,
index: index,
@ -133,6 +79,7 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
.toList();
return ReorderableListView.builder(
padding: EdgeInsets.zero,
proxyDecorator: (_, index, anim) {
final field = fields[index];
return AnimatedBuilder(
@ -146,7 +93,7 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
child: Material(
child: DatabaseFieldListTile(
key: ValueKey(field.id),
viewId: view.id,
viewId: viewId,
fieldController: databaseController.fieldController,
fieldInfo: field,
index: index,
@ -170,7 +117,7 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
_divider(),
_NewDatabaseFieldTile(viewId: view.id),
_NewDatabaseFieldTile(viewId: viewId),
],
),
itemCount: cells.length,

View File

@ -168,6 +168,7 @@ class _CalendarLayoutField extends StatelessWidget {
onTap: () async {
final newFieldId = await showFieldPicker(
context,
LocaleKeys.calendar_settings_changeLayoutDateField.tr(),
selectedFieldId,
databaseController.fieldController,
(field) => field.fieldType == FieldType.DateTime,

View File

@ -1,5 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/plugins/base/drag_handler.dart';
@ -26,28 +27,110 @@ class MobileDatabaseViewList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<ViewBloc, ViewState>(
builder: (context, state) {
final views = [state.view, ...state.view.childViews];
final children = [
...views.mapIndexed(
(index, view) => MobileDatabaseViewListButton(
view: view,
showTopBorder: index == 0,
),
),
const VSpace(20),
const MobileNewDatabaseViewButton(),
];
return DraggableScrollableSheet(
expand: false,
snap: true,
initialChildSize: 0.98,
minChildSize: 0.98,
maxChildSize: 0.98,
builder: (context, scrollController) {
return BlocBuilder<ViewBloc, ViewState>(
builder: (context, state) {
final views = [state.view, ...state.view.childViews];
return Column(
children: children,
return Column(
children: [
const DragHandler(),
_Header(
title: LocaleKeys.grid_settings_viewList.plural(
context.watch<DatabaseTabBarBloc>().state.tabBars.length,
namedArgs: {
'count':
'${context.watch<DatabaseTabBarBloc>().state.tabBars.length}',
},
),
showBackButton: false,
useFilledDoneButton: false,
onDone: (context) => Navigator.pop(context),
),
SingleChildScrollView(
controller: scrollController,
child: Column(
children: [
...views.mapIndexed(
(index, view) => MobileDatabaseViewListButton(
view: view,
showTopBorder: index == 0,
),
),
const VSpace(20),
const MobileNewDatabaseViewButton(),
],
),
),
],
);
},
);
},
);
}
}
/// Same header as the one in showMobileBottomSheet, but allows popping the
/// sheet with a value.
class _Header extends StatelessWidget {
const _Header({
required this.title,
required this.showBackButton,
required this.useFilledDoneButton,
required this.onDone,
});
final String title;
final bool showBackButton;
final bool useFilledDoneButton;
final void Function(BuildContext context) onDone;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 4.0),
child: SizedBox(
height: 44.0,
child: Stack(
children: [
if (showBackButton)
const Align(
alignment: Alignment.centerLeft,
child: AppBarBackButton(),
),
Align(
child: FlowyText.medium(
title,
fontSize: 16.0,
),
),
useFilledDoneButton
? Align(
alignment: Alignment.centerRight,
child: AppBarFilledDoneButton(
onTap: () => onDone(context),
),
)
: Align(
alignment: Alignment.centerRight,
child: AppBarDoneButton(
onTap: () => onDone(context),
),
),
],
),
),
);
}
}
@visibleForTesting
class MobileDatabaseViewListButton extends StatelessWidget {
const MobileDatabaseViewListButton({
@ -155,6 +238,7 @@ class MobileNewDatabaseViewButton extends StatelessWidget {
onTap: () async {
final result = await showMobileBottomSheet<(DatabaseLayoutPB, String)>(
context,
showDragHandle: true,
builder: (_) {
return const MobileCreateDatabaseView();
},
@ -199,12 +283,13 @@ class _MobileCreateDatabaseViewState extends State<MobileCreateDatabaseView> {
Widget build(BuildContext context) {
return Column(
children: [
const Center(child: DragHandler()),
_CreateViewHeader(
textController: controller,
selectedLayout: layoutType,
_Header(
title: LocaleKeys.grid_settings_createView.tr(),
showBackButton: true,
useFilledDoneButton: true,
onDone: (context) =>
context.pop((layoutType, controller.text.trim())),
),
const VSpace(4.0),
FlowyOptionTile.textField(
autofocus: true,
controller: controller,
@ -220,74 +305,3 @@ class _MobileCreateDatabaseViewState extends State<MobileCreateDatabaseView> {
);
}
}
class _CreateViewHeader extends StatelessWidget {
const _CreateViewHeader({
required this.textController,
required this.selectedLayout,
});
final TextEditingController textController;
final DatabaseLayoutPB selectedLayout;
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox.square(
dimension: 36,
child: IconButton(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
padding: EdgeInsets.zero,
onPressed: () => context.pop(),
icon: const FlowySvg(
FlowySvgs.arrow_left_s,
size: Size.square(24),
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(8, 4, 8, 8),
child: TextButton(
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 5,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 0,
visualDensity: VisualDensity.compact,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
enableFeedback: true,
backgroundColor: Theme.of(context).primaryColor,
),
onPressed: () {
context.pop((selectedLayout, textController.text.trim()));
},
child: FlowyText.medium(
LocaleKeys.button_done.tr(),
fontSize: 16,
color: Theme.of(context).colorScheme.onPrimary,
overflow: TextOverflow.ellipsis,
),
),
),
],
),
Center(
child: FlowyText.medium(
LocaleKeys.grid_settings_createView.tr(),
fontSize: 16,
),
),
],
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
import 'package:appflowy/plugins/database/application/database_controller.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
@ -13,7 +14,7 @@ import 'edit_database_view_screen.dart';
/// [MobileDatabaseViewQuickActions] is gives users to quickly edit a database
/// view from the [MobileDatabaseViewList]
class MobileDatabaseViewQuickActions extends StatefulWidget {
class MobileDatabaseViewQuickActions extends StatelessWidget {
const MobileDatabaseViewQuickActions({
super.key,
required this.view,
@ -23,49 +24,46 @@ class MobileDatabaseViewQuickActions extends StatefulWidget {
final ViewPB view;
final DatabaseController databaseController;
@override
State<MobileDatabaseViewQuickActions> createState() =>
_MobileDatabaseViewQuickActionsState();
}
class _MobileDatabaseViewQuickActionsState
extends State<MobileDatabaseViewQuickActions> {
bool isEditing = false;
@override
Widget build(BuildContext context) {
return isEditing
? MobileEditDatabaseViewScreen(
databaseController: widget.databaseController,
)
: _quickActions(context, widget.view);
}
Widget _quickActions(BuildContext context, ViewPB view) {
final isInline = view.childViews.isNotEmpty;
return Padding(
padding: const EdgeInsets.only(top: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_actionButton(context, _Action.edit, () {
setState(() => isEditing = true);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
_actionButton(context, _Action.edit, () {
final bloc = context.read<ViewBloc>();
context.pop();
showMobileBottomSheet(
context,
showHeader: true,
showDoneButton: true,
title: LocaleKeys.grid_settings_editView.tr(),
enableDraggableScrollable: true,
initialChildSize: 0.98,
minChildSize: 0.98,
maxChildSize: 0.98,
builder: (_) => BlocProvider.value(
value: bloc,
child: MobileEditDatabaseViewScreen(
databaseController: databaseController,
),
),
);
}),
if (!isInline) ...[
_divider(),
_actionButton(context, _Action.duplicate, () {
context.read<ViewBloc>().add(const ViewEvent.duplicate());
context.pop();
}),
if (!isInline) ...[
_divider(),
_actionButton(context, _Action.duplicate, () {
context.read<ViewBloc>().add(const ViewEvent.duplicate());
context.pop();
}),
_divider(),
_actionButton(context, _Action.delete, () {
context.read<ViewBloc>().add(const ViewEvent.delete());
context.pop();
}),
_divider(),
],
_divider(),
_actionButton(context, _Action.delete, () {
context.read<ViewBloc>().add(const ViewEvent.delete());
context.pop();
}),
_divider(),
],
),
],
);
}

View File

@ -1,35 +0,0 @@
import 'package:bloc/bloc.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'edit_database_view_cubit.freezed.dart';
class MobileEditDatabaseViewCubit extends Cubit<MobileDatabaseViewEditorState> {
MobileEditDatabaseViewCubit()
: super(
MobileDatabaseViewEditorState.initial(),
);
void changePage(MobileEditDatabaseViewPageEnum newPage) {
emit(MobileDatabaseViewEditorState(currentPage: newPage));
}
}
@freezed
class MobileDatabaseViewEditorState with _$MobileDatabaseViewEditorState {
factory MobileDatabaseViewEditorState({
required MobileEditDatabaseViewPageEnum currentPage,
}) = _MobileDatabaseViewEditorState;
factory MobileDatabaseViewEditorState.initial() =>
MobileDatabaseViewEditorState(
currentPage: MobileEditDatabaseViewPageEnum.main,
);
}
enum MobileEditDatabaseViewPageEnum {
main,
fields,
filter,
sort,
}

View File

@ -2,7 +2,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
import 'package:appflowy/plugins/base/drag_handler.dart';
import 'package:appflowy/plugins/database/application/database_controller.dart';
import 'package:appflowy/plugins/database/application/database_view_service.dart';
import 'package:appflowy/plugins/database/application/layout/layout_service.dart';
@ -18,12 +17,11 @@ import 'package:go_router/go_router.dart';
import 'database_field_list.dart';
import 'database_view_layout.dart';
import 'edit_database_view_cubit.dart';
/// [MobileEditDatabaseViewScreen] is the main widget used to edit a database
/// view. It contains multiple sub-pages, and the current page is managed by
/// [MobileEditDatabaseViewCubit]
class MobileEditDatabaseViewScreen extends StatefulWidget {
class MobileEditDatabaseViewScreen extends StatelessWidget {
const MobileEditDatabaseViewScreen({
super.key,
required this.databaseController,
@ -31,130 +29,12 @@ class MobileEditDatabaseViewScreen extends StatefulWidget {
final DatabaseController databaseController;
@override
State<MobileEditDatabaseViewScreen> createState() =>
_MobileEditDatabaseViewScreenState();
}
class _MobileEditDatabaseViewScreenState
extends State<MobileEditDatabaseViewScreen> {
@override
Widget build(BuildContext context) {
return BlocProvider<MobileEditDatabaseViewCubit>(
create: (context) => MobileEditDatabaseViewCubit(),
child: BlocBuilder<MobileEditDatabaseViewCubit,
MobileDatabaseViewEditorState>(
builder: (context, state) {
return switch (state.currentPage) {
MobileEditDatabaseViewPageEnum.main => _EditDatabaseViewMainPage(
databaseController: widget.databaseController,
),
MobileEditDatabaseViewPageEnum.fields => _wrapSubPage(
context,
MobileDatabaseFieldList(
databaseController: widget.databaseController,
),
),
_ => const SizedBox.shrink(),
};
},
),
);
}
Widget _wrapSubPage(BuildContext context, Widget child) {
return PopScope(
canPop: false,
child: child,
onPopInvoked: (_) {
context
.read<MobileEditDatabaseViewCubit>()
.changePage(MobileEditDatabaseViewPageEnum.main);
},
);
}
}
class _EditDatabaseViewMainPage extends StatelessWidget {
const _EditDatabaseViewMainPage({
required this.databaseController,
});
final DatabaseController databaseController;
@override
Widget build(BuildContext context) {
return DraggableScrollableSheet(
expand: false,
snap: true,
initialChildSize: 1.0,
minChildSize: 0.0,
builder: (context, controller) {
return Material(
child: Column(
children: [
const Center(child: DragHandler()),
const _EditDatabaseViewHeader(),
Expanded(
child: SingleChildScrollView(
child: _EditDatabaseViewBody(
databaseController: databaseController,
),
),
),
],
),
);
},
);
}
}
class _EditDatabaseViewHeader extends StatelessWidget {
const _EditDatabaseViewHeader();
@override
Widget build(BuildContext context) {
const iconWidth = 30.0;
return Padding(
padding: const EdgeInsets.fromLTRB(8, 4, 8, 12),
child: Stack(
children: [
Align(
alignment: Alignment.centerLeft,
child: FlowyIconButton(
icon: const FlowySvg(
FlowySvgs.close_s,
size: Size.square(iconWidth),
),
onPressed: () => context.pop(),
),
),
Align(
child: FlowyText.medium(
LocaleKeys.grid_settings_editView.tr(),
fontSize: 16,
),
),
],
),
);
}
}
class _EditDatabaseViewBody extends StatelessWidget {
const _EditDatabaseViewBody({
required this.databaseController,
});
final DatabaseController databaseController;
@override
Widget build(BuildContext context) {
return BlocBuilder<ViewBloc, ViewState>(
builder: (context, state) {
return Column(
mainAxisSize: MainAxisSize.min,
return ListView(
shrinkWrap: true,
children: [
_NameAndIcon(view: state.view),
_divider(),
@ -207,6 +87,7 @@ class _NameAndIconState extends State<_NameAndIcon> {
Widget build(BuildContext context) {
return FlowyOptionTile.textField(
autofocus: true,
showTopBorder: false,
controller: textEditingController,
onTextChanged: (text) {
context.read<ViewBloc>().add(ViewEvent.rename(text));
@ -250,15 +131,6 @@ enum DatabaseViewSettings {
delete => FlowySvgs.delete_s,
};
}
MobileEditDatabaseViewPageEnum? get subPage {
return switch (this) {
fields => MobileEditDatabaseViewPageEnum.fields,
filter => MobileEditDatabaseViewPageEnum.filter,
sort => MobileEditDatabaseViewPageEnum.sort,
_ => null,
};
}
}
class DatabaseViewSettingTile extends StatelessWidget {
@ -325,25 +197,18 @@ class DatabaseViewSettingTile extends StatelessWidget {
}
void _onTap(BuildContext context) async {
final subPage = setting.subPage;
if (subPage != null) {
context.read<MobileEditDatabaseViewCubit>().changePage(subPage);
return;
}
if (setting == DatabaseViewSettings.layout) {
final databaseLayout = databaseLayoutFromViewLayout(view.layout);
final newLayout = await showMobileBottomSheet<DatabaseLayoutPB>(
context,
resizeToAvoidBottomInset: false,
showDragHandle: true,
showHeader: true,
showDivider: false,
title: LocaleKeys.grid_settings_layout.tr(),
builder: (context) {
return DatabaseViewLayoutPicker(
selectedLayout: databaseLayout,
onSelect: (layout) {
Navigator.of(context).pop(layout);
},
onSelect: (layout) => Navigator.of(context).pop(layout),
);
},
);
@ -356,6 +221,32 @@ class DatabaseViewSettingTile extends StatelessWidget {
return;
}
if (setting == DatabaseViewSettings.fields) {
await showMobileBottomSheet(
context,
useSafeArea: false,
resizeToAvoidBottomInset: false,
showDragHandle: true,
showHeader: true,
showBackButton: true,
title: LocaleKeys.grid_settings_properties.tr(),
showDivider: true,
enableDraggableScrollable: true,
initialChildSize: 0.98,
minChildSize: 0.98,
maxChildSize: 0.98,
builder: (_) {
return BlocProvider.value(
value: context.read<ViewBloc>(),
child: MobileDatabaseFieldList(
databaseController: databaseController,
),
);
},
);
return;
}
if (setting == DatabaseViewSettings.board) {
await showMobileBottomSheet<DatabaseLayoutPB>(
context,
@ -375,13 +266,13 @@ class DatabaseViewSettingTile extends StatelessWidget {
if (setting == DatabaseViewSettings.calendar) {
await showMobileBottomSheet<DatabaseLayoutPB>(
context,
resizeToAvoidBottomInset: false,
showDragHandle: true,
showHeader: true,
showDivider: false,
title: LocaleKeys.calendar_settings_name.tr(),
builder: (context) {
return Padding(
padding: const EdgeInsets.only(top: 24, bottom: 46),
child: MobileCalendarViewLayoutSettings(
databaseController: databaseController,
),
return MobileCalendarViewLayoutSettings(
databaseController: databaseController,
);
},
);

View File

@ -65,7 +65,13 @@ class MobileDatabaseControls extends StatelessWidget {
onTap: () {
showMobileBottomSheet(
context,
padding: EdgeInsets.zero,
showHeader: true,
showDoneButton: true,
title: LocaleKeys.grid_settings_editView.tr(),
enableDraggableScrollable: true,
initialChildSize: 0.98,
minChildSize: 0.98,
maxChildSize: 0.98,
builder: (_) {
return BlocProvider<ViewBloc>(
create: (_) {
@ -90,11 +96,7 @@ class MobileDatabaseControls extends StatelessWidget {
onTap: () {
showMobileBottomSheet(
context,
showHeader: true,
showCloseButton: true,
showDragHandle: true,
showDivider: false,
title: LocaleKeys.grid_settings_viewList.tr(),
builder: (_) {
return MultiBlocProvider(
providers: [

View File

@ -480,7 +480,11 @@
"typeAValue": "Type a value...",
"layout": "Layout",
"databaseLayout": "Layout",
"viewList": "Database Views",
"viewList": {
"zero": "0 views",
"one": "{count} view",
"other": "{count} views"
},
"editView": "Edit View",
"boardSettings": "Board settings",
"calendarSettings": "Calendar settings",
@ -1250,4 +1254,4 @@
"userIcon": "User icon"
},
"noLogFiles": "There're no log files"
}
}