chore: various database UI fixes (#4077)

* chore: only activate row detail from primary field

* chore: fix create filter and sort list

* chore: allow reordering rows when active filter

* chore: reduce animations in tab bar

* chore: url accessory tooltip

* feat: use number keyboard for number cell inputs

* chore: mobile grid padding adjustments

* chore: field cell redesign and first field cannot be reordered

* fix: deal with empty fields

* chore: improve appearance of card action sheet
This commit is contained in:
Richard Shiue 2023-12-04 22:58:02 +08:00 committed by GitHub
parent a070ed2441
commit d4cef2e866
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 172 additions and 90 deletions

View File

@ -14,6 +14,7 @@ Future<T?> showMobileBottomSheet<T>(
bool showHeader = false,
bool showCloseButton = false,
String title = '', // only works if showHeader is true
Color? backgroundColor,
}) async {
assert(() {
if (showCloseButton || title.isNotEmpty) assert(showHeader);
@ -26,6 +27,7 @@ Future<T?> showMobileBottomSheet<T>(
enableDrag: isDragEnabled,
useSafeArea: true,
clipBehavior: Clip.antiAlias,
backgroundColor: backgroundColor,
shape: shape ??
const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(

View File

@ -51,6 +51,10 @@ class _RowDetailNumberCellState
],
child: TextField(
controller: _controller,
keyboardType: const TextInputType.numberWithOptions(
signed: true,
decimal: true,
),
focusNode: focusNode,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontSize: 16),
decoration: InputDecoration(

View File

@ -1,7 +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/show_flowy_mobile_bottom_sheet.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/field/field_controller.dart';
@ -139,15 +138,16 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
}
void _showCardActions(BuildContext context) {
showFlowyMobileBottomSheet(
showMobileBottomSheet(
context,
title: LocaleKeys.board_cardActions.tr(),
builder: (_) => Row(
backgroundColor: Theme.of(context).colorScheme.background,
padding: const EdgeInsets.only(top: 4, bottom: 32),
builder: (_) => Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: BottomSheetActionWidget(
svg: FlowySvgs.copy_s,
text: LocaleKeys.button_duplicate.tr(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: _CardActionButton(
onTap: () {
final rowId = _bloc.state.currentRowId;
if (rowId == null) {
@ -162,13 +162,14 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
gravity: ToastGravity.BOTTOM,
);
},
icon: FlowySvgs.copy_s,
text: LocaleKeys.button_duplicate.tr(),
),
),
const HSpace(8),
Expanded(
child: BottomSheetActionWidget(
svg: FlowySvgs.m_delete_m,
text: LocaleKeys.button_delete.tr(),
const Divider(height: 9),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: _CardActionButton(
onTap: () {
final rowId = _bloc.state.currentRowId;
if (rowId == null) {
@ -183,14 +184,51 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
gravity: ToastGravity.BOTTOM,
);
},
icon: FlowySvgs.m_delete_m,
text: LocaleKeys.button_delete.tr(),
color: Theme.of(context).colorScheme.error,
),
),
const Divider(height: 9),
],
),
);
}
}
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 {
const RowDetailFab({
super.key,
@ -352,10 +390,10 @@ class MobileRowDetailPageContentState
placeholder: LocaleKeys.grid_row_titlePlaceholder.tr(),
textStyle:
Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 22,
fontSize: 23,
fontWeight: FontWeight.w500,
),
cellPadding: const EdgeInsets.symmetric(vertical: 8),
cellPadding: const EdgeInsets.symmetric(vertical: 9),
useRoundedBorder: false,
);
@ -366,7 +404,7 @@ class MobileRowDetailPageContentState
);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 18),
padding: const EdgeInsets.symmetric(horizontal: 16),
child: cellBuilder.build(
cellContext,
style: cellStyle,
@ -379,10 +417,10 @@ class MobileRowDetailPageContentState
),
Expanded(
child: ListView(
padding: const EdgeInsets.only(top: 8, bottom: 100),
padding: const EdgeInsets.only(top: 9, bottom: 100),
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 18),
padding: const EdgeInsets.symmetric(horizontal: 16),
child: MobileRowPropertyList(
cellBuilder: cellBuilder,
viewId: viewId,
@ -390,7 +428,7 @@ class MobileRowDetailPageContentState
),
),
Padding(
padding: const EdgeInsets.all(12),
padding: const EdgeInsets.fromLTRB(6, 6, 16, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

View File

@ -38,11 +38,15 @@ class _CheckboxCellState extends GridCellState<MobileCheckboxCell> {
builder: (context, state) {
return Align(
alignment: Alignment.centerLeft,
// TODO(yijing): improve icon here
child: FlowySvg(
state.isSelected ? FlowySvgs.checkbox_s : FlowySvgs.uncheck_s,
color: Theme.of(context).colorScheme.onBackground,
size: const Size.square(24),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
child: FlowySvg(
state.isSelected
? FlowySvgs.check_filled_s
: FlowySvgs.uncheck_s,
blendMode: BlendMode.dst,
size: const Size.square(24),
),
),
);
},

View File

@ -50,11 +50,13 @@ class _NumberCellState extends GridEditableTextCell<MobileNumberCell> {
child: TextField(
controller: _controller,
focusNode: focusNode,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontSize: 15),
decoration: InputDecoration(
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
hintText: widget.hintText,
contentPadding: EdgeInsets.zero,
contentPadding:
const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
isCollapsed: true,
),
// close keyboard when tapping outside of the text field

View File

@ -56,12 +56,13 @@ class _MobileTextCellState extends GridEditableTextCell<MobileTextCell> {
child: TextField(
controller: _controller,
focusNode: focusNode,
style: widget.cellStyle.textStyle,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontSize: 15),
decoration: InputDecoration(
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
hintText: widget.cellStyle.placeholder,
contentPadding: EdgeInsets.zero,
contentPadding:
const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
isCollapsed: true,
),
onTapOutside: (event) =>

View File

@ -1,6 +1,7 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/timestamp_cell/timestamp_cell_bloc.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -36,7 +37,13 @@ class _TimestampCellState extends GridCellState<MobileTimestampCell> {
builder: (context, state) {
return Align(
alignment: Alignment.centerLeft,
child: Text(state.dateStr),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
child: FlowyText(
state.dateStr,
fontSize: 15,
),
),
);
},
),

View File

@ -59,7 +59,8 @@ class _GridURLCellState extends GridCellState<MobileURLCell> {
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
hintText: widget.hintText,
contentPadding: EdgeInsets.zero,
contentPadding:
const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
isCollapsed: true,
),
// close keyboard when tapping outside of the text field

View File

@ -64,16 +64,13 @@ class GridBloc extends Bloc<GridEvent, GridState> {
},
didReceveFilters: (List<FilterInfo> filters) {
emit(
state.copyWith(
reorderable: filters.isEmpty && state.sorts.isEmpty,
filters: filters,
),
state.copyWith(filters: filters),
);
},
didReceveSorts: (List<SortInfo> sorts) {
emit(
state.copyWith(
reorderable: sorts.isEmpty && state.filters.isEmpty,
reorderable: sorts.isEmpty,
sorts: sorts,
),
);

View File

@ -124,6 +124,7 @@ class _FilterTextFieldDelegate extends SliverPersistentHeaderDelegate {
) {
return Container(
padding: const EdgeInsets.only(bottom: 4),
color: Theme.of(context).cardColor,
height: fixHeight,
child: FlowyTextField(
hintText: LocaleKeys.grid_settings_filterBy.tr(),

View File

@ -16,7 +16,7 @@ class GridAddRowButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FlowyButton(
text: FlowyText.medium(
text: FlowyText(
LocaleKeys.grid_row_newRow.tr(),
color: Theme.of(context).hintColor,
),

View File

@ -2,6 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.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/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/mobile_field_cell.dart';
@ -92,7 +93,13 @@ class _GridHeaderState extends State<_GridHeader> {
Widget build(BuildContext context) {
return BlocBuilder<GridHeaderBloc, GridHeaderState>(
builder: (context, state) {
final cells = state.fields
final fields = [...state.fields];
FieldInfo? firstField;
if (PlatformExtension.isMobile && fields.isNotEmpty) {
firstField = fields.removeAt(0);
}
final cells = fields
.map(
(fieldInfo) => PlatformExtension.isDesktop
? GridFieldCell(
@ -129,7 +136,7 @@ class _GridHeaderState extends State<_GridHeader> {
child: child,
),
draggingWidgetOpacity: 0,
header: const _CellLeading(),
header: _cellLeading(firstField),
needsLongPressDraggable: PlatformExtension.isMobile,
footer: _CellTrailing(viewId: widget.viewId),
onReorder: (int oldIndex, int newIndex) {
@ -162,16 +169,25 @@ class _GridHeaderState extends State<_GridHeader> {
.add(GridHeaderEvent.moveField(field, oldIndex, newIndex));
}
}
}
class _CellLeading extends StatelessWidget {
const _CellLeading({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
width: GridSize.leadingHeaderPadding,
);
Widget _cellLeading(FieldInfo? fieldInfo) {
if (PlatformExtension.isDesktop) {
return SizedBox(width: GridSize.leadingHeaderPadding);
} else {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(width: GridSize.leadingHeaderPadding),
if (fieldInfo != null)
MobileFieldButton(
key: _getKeyById(fieldInfo.id),
viewId: widget.viewId,
fieldController: widget.fieldController,
fieldInfo: fieldInfo,
),
],
);
}
}
}
@ -222,10 +238,11 @@ class _CreateFieldButtonState extends State<CreateFieldButton> {
return FlowyButton(
margin: PlatformExtension.isDesktop
? GridSize.cellContentInsets
: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
: const EdgeInsets.symmetric(vertical: 14, horizontal: 12),
radius: BorderRadius.zero,
text: FlowyText.medium(
text: FlowyText(
LocaleKeys.grid_field_newProperty.tr(),
fontSize: 15,
overflow: TextOverflow.ellipsis,
color: PlatformExtension.isDesktop ? null : Theme.of(context).hintColor,
),
@ -245,6 +262,7 @@ class _CreateFieldButtonState extends State<CreateFieldButton> {
},
leftIcon: FlowySvg(
FlowySvgs.add_s,
size: const Size.square(18),
color: PlatformExtension.isDesktop ? null : Theme.of(context).hintColor,
),
);

View File

@ -27,31 +27,22 @@ class MobileFieldButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
return SizedBox(
width: fieldInfo.fieldSettings!.width.toDouble(),
decoration: BoxDecoration(
border: Border(
right: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0,
),
),
),
child: FlowyButton(
onTap: () {
showEditFieldScreen(context, viewId, fieldInfo);
},
onTap: () => showEditFieldScreen(context, viewId, fieldInfo),
radius: BorderRadius.zero,
margin: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 12),
leftIconSize: const Size.square(18),
leftIcon: FlowySvg(
fieldInfo.fieldType.icon(),
color: Theme.of(context).hintColor,
size: const Size.square(18),
),
text: FlowyText.medium(
text: FlowyText(
fieldInfo.name,
fontSize: 15,
maxLines: maxLines,
overflow: TextOverflow.ellipsis,
color: Theme.of(context).hintColor,
),
),
);

View File

@ -71,14 +71,9 @@ class _MobileGridRowState extends State<MobileGridRow> {
children: [
SizedBox(width: GridSize.leadingHeaderPadding),
Expanded(
child: InkWell(
onTap: () => widget.openDetailPage(context),
child: IgnorePointer(
child: RowContent(
builder: _cellBuilder,
onExpand: () => widget.openDetailPage(context),
),
),
child: RowContent(
builder: _cellBuilder,
onExpand: () => widget.openDetailPage(context),
),
),
],
@ -155,9 +150,8 @@ class RowContent extends StatelessWidget {
return MobileCellContainer(
width: cellId.fieldInfo.fieldSettings!.width.toDouble(),
isPrimary: cellId.fieldInfo.field.isPrimary,
accessoryBuilder: (buildContext) {
return [];
},
accessoryBuilder: (_) => [],
onPrimaryFieldCellTap: onExpand,
child: child,
);
},

View File

@ -124,6 +124,7 @@ class _SortTextFieldDelegate extends SliverPersistentHeaderDelegate {
) {
return Container(
padding: const EdgeInsets.only(bottom: 4),
color: Theme.of(context).cardColor,
height: fixHeight,
child: FlowyTextField(
hintText: LocaleKeys.grid_settings_sortBy.tr(),

View File

@ -77,11 +77,7 @@ class _DatabaseTabBarViewState extends State<DatabaseTabBarView> {
BlocListener<DatabaseTabBarBloc, DatabaseTabBarState>(
listenWhen: (p, c) => p.selectedIndex != c.selectedIndex,
listener: (context, state) {
_pageController?.animateToPage(
state.selectedIndex,
duration: const Duration(milliseconds: 300),
curve: Curves.ease,
);
_pageController?.jumpToPage(state.selectedIndex);
},
),
],

View File

@ -154,10 +154,12 @@ class _DateCellState extends GridCellState<GridDateCell> {
);
} else {
return FlowyButton(
radius: BorderRadius.zero,
hoverColor: Colors.transparent,
text: Container(
alignment: alignment,
padding: padding,
child: FlowyText.medium(text, color: color),
child: FlowyText(text, color: color, fontSize: 15),
),
onTap: () {
showMobileBottomSheet(

View File

@ -13,6 +13,7 @@ class MobileCellContainer extends StatelessWidget {
final AccessoryBuilder? accessoryBuilder;
final double width;
final bool isPrimary;
final VoidCallback? onPrimaryFieldCellTap;
const MobileCellContainer({
super.key,
@ -20,6 +21,7 @@ class MobileCellContainer extends StatelessWidget {
required this.width,
required this.isPrimary,
this.accessoryBuilder,
this.onPrimaryFieldCellTap,
});
@override
@ -48,9 +50,17 @@ class MobileCellContainer extends StatelessWidget {
}
}
if (isPrimary) {
container = IgnorePointer(child: container);
}
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
if (isPrimary) {
onPrimaryFieldCellTap?.call();
return;
}
if (!isFocus) {
child.requestFocus.notify();
}

View File

@ -246,6 +246,8 @@ class _SelectOptionWrapState extends State<SelectOptionWrap> {
);
} else {
return FlowyButton(
hoverColor: Colors.transparent,
radius: BorderRadius.zero,
text: Padding(
padding: widget.cellStyle.cellPadding ?? GridSize.cellContentInsets,
child: _buildMobileOptions(isInRowDetail: false),

View File

@ -6,6 +6,7 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_
import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -200,10 +201,14 @@ class _CopyURLAccessoryState extends State<_CopyURLAccessory>
@override
Widget build(BuildContext context) {
if (widget.cellDataNotifier.value.isNotEmpty) {
return _URLAccessoryIconContainer(
child: FlowySvg(
FlowySvgs.copy_s,
color: AFThemeExtension.of(context).textColor,
return FlowyTooltip(
message: LocaleKeys.tooltip_urlCopyAccessory.tr(),
preferBelow: false,
child: _URLAccessoryIconContainer(
child: FlowySvg(
FlowySvgs.copy_s,
color: AFThemeExtension.of(context).textColor,
),
),
);
} else {
@ -242,10 +247,14 @@ class _VisitURLAccessoryState extends State<_VisitURLAccessory>
@override
Widget build(BuildContext context) {
if (widget.cellDataNotifier.value.isNotEmpty) {
return _URLAccessoryIconContainer(
child: FlowySvg(
FlowySvgs.attach_s,
color: AFThemeExtension.of(context).textColor,
return FlowyTooltip(
message: LocaleKeys.tooltip_urlLaunchAccessory.tr(),
preferBelow: false,
child: _URLAccessoryIconContainer(
child: FlowySvg(
FlowySvgs.attach_s,
color: AFThemeExtension.of(context).textColor,
),
),
);
} else {

View File

@ -177,7 +177,9 @@
"dragRow": "Long press to reorder the row",
"viewDataBase": "View database",
"referencePage": "This {name} is referenced",
"addBlockBelow": "Add a block below"
"addBlockBelow": "Add a block below",
"urlLaunchAccessory": "Open in browser",
"urlCopyAccessory": "Copy URL"
},
"sideBar": {
"closeSidebar": "Close side bar",