feat: add borders on mobile grid (#4146)

* feat: mobile grid ui improvements

* chore: restore Podfile.lock
This commit is contained in:
Richard Shiue 2023-12-14 23:15:26 +08:00 committed by GitHub
parent d26606d3d3
commit d862455342
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 494 additions and 305 deletions

View File

@ -20,7 +20,7 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/disclosure_button.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/filter_menu_item.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/desktop_field_cell.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_list.dart';

View File

@ -4,6 +4,7 @@ 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/card/card.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/widgets/card/card.dart';
import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart';
@ -81,31 +82,47 @@ class _MobileBoardContentState extends State<MobileBoardContent> {
final showCreateGroupButton =
context.read<BoardBloc>().groupingFieldType.canCreateNewGroup;
final showHiddenGroups = state.hiddenGroups.isNotEmpty;
return AppFlowyBoard(
boardScrollController: scrollManager,
scrollController: scrollController,
controller: context.read<BoardBloc>().boardController,
groupConstraints: BoxConstraints.tightFor(width: screenWidth * 0.7),
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,
return Column(
children: [
Divider(
height: 1,
thickness: 1,
indent: GridSize.leadingHeaderPadding / 2,
endIndent: GridSize.leadingHeaderPadding / 2,
),
),
footerBuilder: _buildFooter,
cardBuilder: (_, column, columnItem) => _buildCard(
context: context,
afGroupData: column,
afGroupItem: columnItem,
cardMargin: config.cardMargin,
),
Expanded(
child: AppFlowyBoard(
boardScrollController: scrollManager,
scrollController: scrollController,
controller: context.read<BoardBloc>().boardController,
groupConstraints:
BoxConstraints.tightFor(width: screenWidth * 0.7),
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,
),
),
),
],
);
},
),

View File

@ -186,28 +186,45 @@ class _CalendarPageState extends State<CalendarPage> {
EventController eventController,
int firstDayOfWeek,
) {
return Padding(
padding: PlatformExtension.isMobile
? CalendarSize.contentInsetsMobile
: CalendarSize.contentInsets,
child: LayoutBuilder(
// must specify MonthView width for useAvailableVerticalSpace to work properly
builder: (context, constraints) => ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
child: MonthView(
key: _calendarState,
controller: _eventController,
width: constraints.maxWidth,
cellAspectRatio: PlatformExtension.isMobile ? 0.9 : 0.6,
startDay: _weekdayFromInt(firstDayOfWeek),
showBorder: false,
headerBuilder: _headerNavigatorBuilder,
weekDayBuilder: _headerWeekDayBuilder,
cellBuilder: _calendarDayBuilder,
useAvailableVerticalSpace: widget.shrinkWrap,
return LayoutBuilder(
// must specify MonthView width for useAvailableVerticalSpace to work properly
builder: (context, constraints) {
Widget calendar = Padding(
padding: PlatformExtension.isMobile
? CalendarSize.contentInsetsMobile
: CalendarSize.contentInsets,
child: ScrollConfiguration(
behavior:
ScrollConfiguration.of(context).copyWith(scrollbars: false),
child: MonthView(
key: _calendarState,
controller: _eventController,
width: constraints.maxWidth,
cellAspectRatio: PlatformExtension.isMobile ? 0.9 : 0.6,
startDay: _weekdayFromInt(firstDayOfWeek),
showBorder: false,
headerBuilder: _headerNavigatorBuilder,
weekDayBuilder: _headerWeekDayBuilder,
cellBuilder: _calendarDayBuilder,
useAvailableVerticalSpace: widget.shrinkWrap,
),
),
),
),
);
if (PlatformExtension.isMobile) {
calendar = Column(
children: [
Divider(
height: 1,
thickness: 1,
indent: GridSize.leadingHeaderPadding / 2,
endIndent: GridSize.leadingHeaderPadding / 2,
),
Expanded(child: calendar),
],
);
}
return calendar;
},
);
}

View File

@ -1,9 +1,10 @@
import 'package:appflowy/generated/flowy_svgs.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/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/application/row/row_cache.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/presentation/widgets/shortcuts.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';
@ -21,13 +22,10 @@ import 'package:linked_scroll_controller/linked_scroll_controller.dart';
import 'grid_page.dart';
import 'grid_scroll.dart';
import 'layout/layout.dart';
import 'layout/sizes.dart';
import 'widgets/footer/grid_footer.dart';
import 'widgets/header/grid_header.dart';
import 'widgets/header/mobile_grid_header.dart';
import 'widgets/mobile_fab.dart';
import 'widgets/row/mobile_row.dart';
import 'widgets/shortcuts.dart';
class MobileGridTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
final _toggleExtension = ToggleExtensionNotifier();
@ -102,9 +100,7 @@ class _MobileGridPageState extends State<MobileGridPage> {
loading: (_) =>
const Center(child: CircularProgressIndicator.adaptive()),
finish: (result) => result.successOrFail.fold(
(_) => GridShortcuts(
child: GridPageContent(view: widget.view),
),
(_) => GridShortcuts(child: GridPageContent(view: widget.view)),
(err) => FlowyErrorPage.message(
err.toString(),
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
@ -170,11 +166,8 @@ class _GridPageContentState extends State<GridPageContent> {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(right: GridSize.leadingHeaderPadding),
child: _GridHeader(
headerScrollController: headerScrollController,
),
_GridHeader(
headerScrollController: headerScrollController,
),
_GridRows(
viewId: widget.view.id,
@ -201,7 +194,7 @@ class _GridHeader extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<GridBloc, GridState>(
builder: (context, state) {
return GridHeaderSliverAdaptor(
return MobileGridHeader(
viewId: state.viewId,
anchorScrollController: headerScrollController,
);
@ -224,7 +217,8 @@ class _GridRows extends StatelessWidget {
return BlocBuilder<GridBloc, GridState>(
buildWhen: (previous, current) => previous.fields != current.fields,
builder: (context, state) {
final contentWidth = GridLayout.headerWidth(state.fields);
final double contentWidth =
(state.fields.length + 1) * 200 + GridSize.leadingHeaderPadding;
return Expanded(
child: _WrapScrollView(
scrollController: scrollController,
@ -238,14 +232,13 @@ class _GridRows extends StatelessWidget {
orElse: () => false,
),
builder: (context, state) {
final rowInfos = state.rowInfos;
final behavior = ScrollConfiguration.of(context).copyWith(
scrollbars: false,
physics: const ClampingScrollPhysics(),
);
return ScrollConfiguration(
behavior: behavior,
child: _renderList(context, contentWidth, state, rowInfos),
child: _renderList(context, state),
);
},
),
@ -257,11 +250,9 @@ class _GridRows extends StatelessWidget {
Widget _renderList(
BuildContext context,
double contentWidth,
GridState state,
List<RowInfo> rowInfos,
) {
final children = rowInfos.mapIndexed((index, rowInfo) {
final children = state.rowInfos.mapIndexed((index, rowInfo) {
return ReorderableDelayedDragStartListener(
key: ValueKey(rowInfo.rowMeta.id),
index: index,
@ -288,27 +279,14 @@ class _GridRows extends StatelessWidget {
}
context.read<GridBloc>().add(GridEvent.moveRow(fromIndex, toIndex));
},
itemCount: rowInfos.length,
itemCount: state.rowInfos.length,
itemBuilder: (context, index) => children[index],
header: Padding(
padding: EdgeInsets.only(left: GridSize.leadingHeaderPadding),
child: Container(
height: 1,
width: contentWidth,
color: Theme.of(context).dividerColor,
),
),
footer: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: GridSize.footerContentInsets,
child: const SizedBox(
height: 42,
child: GridAddRowButton(
key: Key('gridFooter'),
),
),
child: _AddRowButton(),
),
Container(
height: 30,
@ -431,3 +409,46 @@ class _GridFooter extends StatelessWidget {
);
}
}
class _AddRowButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final borderSide = BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0,
);
const radius = BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
);
final decoration = BoxDecoration(
borderRadius: radius,
border: BorderDirectional(
start: borderSide,
end: borderSide,
bottom: borderSide,
),
);
return Container(
height: 54,
decoration: decoration,
child: FlowyButton(
text: FlowyText(
LocaleKeys.grid_row_newRow.tr(),
fontSize: 15,
color: Theme.of(context).hintColor,
),
margin: const EdgeInsets.symmetric(horizontal: 20.0),
radius: radius,
hoverColor: AFThemeExtension.of(context).lightGreyHover,
onTap: () => context.read<GridBloc>().add(const GridEvent.createRow()),
leftIcon: FlowySvg(
FlowySvgs.add_s,
color: Theme.of(context).hintColor,
size: const Size.square(18),
),
leftIconSize: const Size.square(18),
),
);
}
}

View File

@ -1,12 +1,9 @@
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/application/field/field_service.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';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:easy_localization/easy_localization.dart';
@ -17,7 +14,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:reorderables/reorderables.dart';
import '../../layout/sizes.dart';
import 'field_cell.dart';
import 'desktop_field_cell.dart';
class GridHeaderSliverAdaptor extends StatefulWidget {
final String viewId;
@ -81,38 +78,25 @@ class _GridHeaderState extends State<_GridHeader> {
Widget build(BuildContext context) {
return BlocBuilder<GridHeaderBloc, GridHeaderState>(
builder: (context, state) {
final fields = [...state.fields];
FieldInfo? firstField;
if (PlatformExtension.isMobile && fields.isNotEmpty) {
firstField = fields.removeAt(0);
}
final cells = fields
final cells = state.fields
.map(
(fieldInfo) => PlatformExtension.isDesktop
? GridFieldCell(
key: _getKeyById(fieldInfo.id),
viewId: widget.viewId,
fieldInfo: fieldInfo,
fieldController: widget.fieldController,
onTap: () => context
.read<GridHeaderBloc>()
.add(GridHeaderEvent.startEditingField(fieldInfo.id)),
onFieldInsertedOnEitherSide: (fieldId) => context
.read<GridHeaderBloc>()
.add(GridHeaderEvent.startEditingNewField(fieldId)),
onEditorOpened: () => context
.read<GridHeaderBloc>()
.add(const GridHeaderEvent.endEditingField()),
isEditing: state.editingFieldId == fieldInfo.id,
isNew: state.newFieldId == fieldInfo.id,
)
: MobileFieldButton(
key: _getKeyById(fieldInfo.id),
viewId: widget.viewId,
fieldController: widget.fieldController,
fieldInfo: fieldInfo,
),
(fieldInfo) => GridFieldCell(
key: _getKeyById(fieldInfo.id),
viewId: widget.viewId,
fieldInfo: fieldInfo,
fieldController: widget.fieldController,
onTap: () => context
.read<GridHeaderBloc>()
.add(GridHeaderEvent.startEditingField(fieldInfo.id)),
onFieldInsertedOnEitherSide: (fieldId) => context
.read<GridHeaderBloc>()
.add(GridHeaderEvent.startEditingNewField(fieldId)),
onEditorOpened: () => context
.read<GridHeaderBloc>()
.add(const GridHeaderEvent.endEditingField()),
isEditing: state.editingFieldId == fieldInfo.id,
isNew: state.newFieldId == fieldInfo.id,
),
)
.toList();
@ -124,15 +108,10 @@ class _GridHeaderState extends State<_GridHeader> {
child: child,
),
draggingWidgetOpacity: 0,
header: _cellLeading(firstField),
header: _cellLeading(),
needsLongPressDraggable: PlatformExtension.isMobile,
footer: _CellTrailing(viewId: widget.viewId),
onReorder: (int oldIndex, int newIndex) {
// to offset removing the first field from `state.fields`
if (PlatformExtension.isMobile) {
oldIndex++;
newIndex++;
}
context
.read<GridHeaderBloc>()
.add(GridHeaderEvent.moveField(oldIndex, newIndex));
@ -157,24 +136,8 @@ class _GridHeaderState extends State<_GridHeader> {
return newKey;
}
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,
),
],
);
}
Widget _cellLeading() {
return SizedBox(width: GridSize.leadingHeaderPadding);
}
}
@ -185,15 +148,13 @@ class _CellTrailing extends StatelessWidget {
@override
Widget build(BuildContext context) {
final borderSide =
BorderSide(color: Theme.of(context).dividerColor, width: 1.0);
return Container(
width: GridSize.trailHeaderPadding,
decoration: PlatformExtension.isDesktop
? BoxDecoration(
border: Border(bottom: borderSide),
)
: null,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Theme.of(context).dividerColor, width: 1.0),
),
),
padding: GridSize.headerContentInsets,
child: CreateFieldButton(
viewId: viewId,
@ -223,34 +184,25 @@ class _CreateFieldButtonState extends State<CreateFieldButton> {
@override
Widget build(BuildContext context) {
return FlowyButton(
margin: PlatformExtension.isDesktop
? GridSize.cellContentInsets
: const EdgeInsets.symmetric(vertical: 14, horizontal: 12),
margin: GridSize.cellContentInsets,
radius: BorderRadius.zero,
text: FlowyText(
LocaleKeys.grid_field_newProperty.tr(),
fontSize: PlatformExtension.isDesktop ? null : 15,
overflow: TextOverflow.ellipsis,
color: PlatformExtension.isDesktop ? null : Theme.of(context).hintColor,
),
hoverColor: AFThemeExtension.of(context).greyHover,
onTap: () async {
if (PlatformExtension.isMobile) {
showCreateFieldBottomSheet(context, widget.viewId);
} else {
final result = await FieldBackendService.createField(
viewId: widget.viewId,
);
result.fold(
(typeOptionPB) => widget.onFieldCreated(typeOptionPB.field_2.id),
(err) => Log.error("Failed to create field type option: $err"),
);
}
final result = await FieldBackendService.createField(
viewId: widget.viewId,
);
result.fold(
(typeOptionPB) => widget.onFieldCreated(typeOptionPB.field_2.id),
(err) => Log.error("Failed to create field type option: $err"),
);
},
leftIcon: FlowySvg(
leftIcon: const FlowySvg(
FlowySvgs.add_s,
size: const Size.square(18),
color: PlatformExtension.isDesktop ? null : Theme.of(context).hintColor,
size: Size.square(18),
),
);
}

View File

@ -9,30 +9,39 @@ import 'field_type_extension.dart';
class MobileFieldButton extends StatelessWidget {
final String viewId;
final int? index;
final FieldController fieldController;
final FieldInfo fieldInfo;
final int? maxLines;
final BorderRadius? radius;
final EdgeInsets? margin;
const MobileFieldButton({
super.key,
required this.viewId,
required this.fieldController,
required this.fieldInfo,
this.maxLines = 1,
this.radius = BorderRadius.zero,
this.margin,
required this.index,
}) : radius = BorderRadius.zero,
margin = const EdgeInsets.symmetric(vertical: 14, horizontal: 12);
const MobileFieldButton.first({
super.key,
});
required this.viewId,
required this.fieldController,
required this.fieldInfo,
}) : radius = const BorderRadius.only(topLeft: Radius.circular(24)),
margin = const EdgeInsets.symmetric(vertical: 14, horizontal: 18),
index = null;
@override
Widget build(BuildContext context) {
return SizedBox(
width: fieldInfo.fieldSettings!.width.toDouble(),
Widget child = Container(
width: 200,
decoration: _getDecoration(context),
child: FlowyButton(
onTap: () => showQuickEditField(context, viewId, fieldInfo),
radius: BorderRadius.zero,
margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 12),
radius: radius,
margin: margin,
leftIconSize: const Size.square(18),
leftIcon: FlowySvg(
fieldInfo.fieldType.icon(),
@ -41,10 +50,36 @@ class MobileFieldButton extends StatelessWidget {
text: FlowyText(
fieldInfo.name,
fontSize: 15,
maxLines: maxLines,
overflow: TextOverflow.ellipsis,
),
),
);
if (index != null) {
child = ReorderableDelayedDragStartListener(index: index!, child: child);
}
return child;
}
BoxDecoration? _getDecoration(BuildContext context) {
final borderSide = BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0,
);
if (index == null) {
return BoxDecoration(
borderRadius: const BorderRadiusDirectional.only(
topStart: Radius.circular(24),
),
border: BorderDirectional(
top: borderSide,
start: borderSide,
),
);
} else {
return null;
}
}
}

View File

@ -0,0 +1,212 @@
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:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../layout/sizes.dart';
import 'mobile_field_button.dart';
class MobileGridHeader extends StatefulWidget {
final String viewId;
final ScrollController anchorScrollController;
const MobileGridHeader({
required this.viewId,
required this.anchorScrollController,
super.key,
});
@override
State<MobileGridHeader> createState() => _MobileGridHeaderState();
}
class _MobileGridHeaderState extends State<MobileGridHeader> {
@override
Widget build(BuildContext context) {
final fieldController =
context.read<GridBloc>().databaseController.fieldController;
return BlocProvider(
create: (context) {
return GridHeaderBloc(
viewId: widget.viewId,
fieldController: fieldController,
)..add(const GridHeaderEvent.initial());
},
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: widget.anchorScrollController,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
HSpace(GridSize.leadingHeaderPadding),
Stack(
children: [
Positioned(top: 0, left: 24, right: 24, child: _divider()),
Positioned(bottom: 0, left: 0, right: 0, child: _divider()),
SizedBox(
height: 50,
child: _GridHeader(
viewId: widget.viewId,
fieldController: fieldController,
),
),
],
),
const HSpace(20),
],
),
),
);
}
Widget _divider() {
return Divider(
height: 1,
thickness: 1,
color: Theme.of(context).dividerColor,
);
}
}
class _GridHeader extends StatefulWidget {
final String viewId;
final FieldController fieldController;
const _GridHeader({
required this.viewId,
required this.fieldController,
});
@override
State<_GridHeader> createState() => _GridHeaderState();
}
class _GridHeaderState extends State<_GridHeader> {
@override
Widget build(BuildContext context) {
return BlocBuilder<GridHeaderBloc, GridHeaderState>(
builder: (context, state) {
final fields = [...state.fields];
FieldInfo? firstField;
if (fields.isNotEmpty) {
firstField = fields.removeAt(0);
}
final cells = fields
.mapIndexed(
(index, fieldInfo) => MobileFieldButton(
key: ValueKey(fieldInfo.id),
index: index,
viewId: widget.viewId,
fieldController: widget.fieldController,
fieldInfo: fieldInfo,
),
)
.toList();
return ReorderableListView.builder(
scrollController: ScrollController(),
shrinkWrap: true,
scrollDirection: Axis.horizontal,
proxyDecorator: (child, index, anim) => Material(
color: Colors.transparent,
child: child,
),
header: firstField != null
? MobileFieldButton.first(
viewId: widget.viewId,
fieldController: widget.fieldController,
fieldInfo: firstField,
)
: null,
footer: CreateFieldButton(
viewId: widget.viewId,
onFieldCreated: (fieldId) => context
.read<GridHeaderBloc>()
.add(GridHeaderEvent.startEditingNewField(fieldId)),
),
onReorder: (int oldIndex, int newIndex) {
if (oldIndex < newIndex) {
newIndex--;
}
oldIndex++;
newIndex++;
context
.read<GridHeaderBloc>()
.add(GridHeaderEvent.moveField(oldIndex, newIndex));
},
itemCount: cells.length,
itemBuilder: (context, index) => cells[index],
);
},
);
}
}
class CreateFieldButton extends StatefulWidget {
const CreateFieldButton({
super.key,
required this.viewId,
required this.onFieldCreated,
});
final String viewId;
final void Function(String fieldId) onFieldCreated;
@override
State<CreateFieldButton> createState() => _CreateFieldButtonState();
}
class _CreateFieldButtonState extends State<CreateFieldButton> {
@override
Widget build(BuildContext context) {
return Container(
width: 200,
decoration: _getDecoration(context),
child: FlowyButton(
margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 12),
radius: const BorderRadius.only(topRight: Radius.circular(24)),
text: FlowyText(
LocaleKeys.grid_field_newProperty.tr(),
fontSize: 15,
overflow: TextOverflow.ellipsis,
color: Theme.of(context).hintColor,
),
hoverColor: AFThemeExtension.of(context).greyHover,
onTap: () => showCreateFieldBottomSheet(context, widget.viewId),
leftIconSize: const Size.square(18),
leftIcon: FlowySvg(
FlowySvgs.add_s,
size: const Size.square(18),
color: Theme.of(context).hintColor,
),
),
);
}
BoxDecoration? _getDecoration(BuildContext context) {
final borderSide = BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0,
);
return BoxDecoration(
borderRadius: const BorderRadiusDirectional.only(
topEnd: Radius.circular(24),
),
border: BorderDirectional(
top: borderSide,
end: borderSide,
),
);
}
}

View File

@ -27,9 +27,9 @@ Widget getGridFabs(BuildContext context) {
}
},
boxShadow: const BoxShadow(
offset: Offset(0, 6),
offset: Offset(0, 8),
color: Color(0x145D7D8B),
blurRadius: 18,
blurRadius: 20,
),
icon: FlowySvgs.properties_s,
iconSize: const Size.square(24),
@ -81,12 +81,13 @@ class MobileGridFab extends StatelessWidget {
return DecoratedBox(
decoration: BoxDecoration(
color: backgroundColor,
border: const Border.fromBorderSide(BorderSide( width: 0.5, color: Color(0xFFE4EDF0))),
borderRadius: radius,
boxShadow: [boxShadow],
),
child: Material(
borderOnForeground: false,
color: backgroundColor,
color: Colors.transparent,
borderRadius: radius,
child: InkWell(
borderRadius: radius,

View File

@ -126,7 +126,8 @@ class RowContent extends StatelessWidget {
buildWhen: (previous, current) =>
!listEquals(previous.cells, current.cells),
builder: (context, state) {
return IntrinsicHeight(
return SizedBox(
height: 52,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@ -148,9 +149,7 @@ class RowContent extends StatelessWidget {
final GridCellWidget child = builder.build(cellId);
return MobileCellContainer(
width: cellId.fieldInfo.fieldSettings!.width.toDouble(),
isPrimary: cellId.fieldInfo.field.isPrimary,
accessoryBuilder: (_) => [],
onPrimaryFieldCellTap: onExpand,
child: child,
);
@ -159,16 +158,14 @@ class RowContent extends StatelessWidget {
}
Widget _finalCellDecoration(BuildContext context) {
return MouseRegion(
cursor: SystemMouseCursors.basic,
child: Container(
width: GridSize.trailHeaderPadding,
padding: GridSize.headerContentInsets,
constraints: const BoxConstraints(minHeight: 46),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Theme.of(context).dividerColor),
),
return Container(
width: 200,
padding: GridSize.headerContentInsets,
constraints: const BoxConstraints(minHeight: 46),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Theme.of(context).dividerColor),
right: BorderSide(color: Theme.of(context).dividerColor),
),
),
);

View File

@ -27,7 +27,6 @@ class _MobileTabBarHeaderState extends State<MobileTabBarHeader> {
@override
void dispose() {
controller.dispose();
super.dispose();
}
@ -45,55 +44,45 @@ class _MobileTabBarHeaderState extends State<MobileTabBarHeader> {
controller.text = currentView.view.name;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 14),
child: Row(
children: [
_buildViewIconButton(currentView.view),
const HSpace(8.0),
Expanded(
child: FlowyTextField(
autoFocus: false,
maxLines: null,
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;
},
),
return Padding(
padding: const EdgeInsets.symmetric(vertical: 14),
child: Row(
children: [
_buildViewIconButton(currentView.view),
const HSpace(8.0),
Expanded(
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,
),
MobileDatabaseSettingsButton(
controller: state
.tabBarControllerByViewId[currentView.viewId]!
.controller,
toggleExtension: ToggleExtensionNotifier(),
),
],
textStyle: Theme.of(context).textTheme.titleLarge,
onSubmitted: (value) {
if (value.isNotEmpty) {
context.read<ViewBloc>().add(
ViewEvent.rename(value),
);
}
},
onCanceled: () {
controller.text = currentView.view.name;
},
),
),
),
const Divider(
height: 1,
thickness: 1,
),
],
MobileDatabaseSettingsButton(
controller: state
.tabBarControllerByViewId[currentView.viewId]!.controller,
toggleExtension: ToggleExtensionNotifier(),
),
],
),
);
},
);

View File

@ -1,26 +1,19 @@
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import '../accessory/cell_accessory.dart';
import '../accessory/cell_shortcuts.dart';
import '../cell_builder.dart';
import 'cell_container.dart';
class MobileCellContainer extends StatelessWidget {
final GridCellWidget child;
final AccessoryBuilder? accessoryBuilder;
final double width;
final bool isPrimary;
final VoidCallback? onPrimaryFieldCellTap;
const MobileCellContainer({
super.key,
required this.child,
required this.width,
required this.isPrimary,
this.accessoryBuilder,
this.onPrimaryFieldCellTap,
});
@ -33,23 +26,6 @@ class MobileCellContainer extends StatelessWidget {
builder: (providerContext, isFocus, _) {
Widget container = Center(child: GridCellShortcuts(child: child));
if (accessoryBuilder != null) {
final accessories = accessoryBuilder!.call(
GridCellAccessoryBuildContext(
anchorContext: context,
isCellEditing: isFocus,
),
);
if (accessories.isNotEmpty) {
container = _GridCellEnterRegion(
accessories: accessories,
isPrimary: isPrimary,
child: container,
);
}
}
if (isPrimary) {
container = IgnorePointer(child: container);
}
@ -66,8 +42,8 @@ class MobileCellContainer extends StatelessWidget {
}
},
child: Container(
constraints: BoxConstraints(maxWidth: width, minHeight: 46),
decoration: _makeBoxDecoration(context, isFocus),
constraints: const BoxConstraints(maxWidth: 200, minHeight: 46),
decoration: _makeBoxDecoration(context, isPrimary, isFocus),
child: container,
),
);
@ -76,7 +52,11 @@ class MobileCellContainer extends StatelessWidget {
);
}
BoxDecoration _makeBoxDecoration(BuildContext context, bool isFocus) {
BoxDecoration _makeBoxDecoration(
BuildContext context,
bool isPrimary,
bool isFocus,
) {
if (isFocus) {
return BoxDecoration(
border: Border.fromBorderSide(
@ -89,43 +69,11 @@ class MobileCellContainer extends StatelessWidget {
final borderSide = BorderSide(color: Theme.of(context).dividerColor);
return BoxDecoration(
border: Border(right: borderSide, bottom: borderSide),
);
}
}
class _GridCellEnterRegion extends StatelessWidget {
const _GridCellEnterRegion({
required this.child,
required this.accessories,
required this.isPrimary,
});
final Widget child;
final List<GridCellAccessoryBuilder> accessories;
final bool isPrimary;
@override
Widget build(BuildContext context) {
return Selector<CellContainerNotifier, bool>(
selector: (context, cellNotifier) => !cellNotifier.isFocus && isPrimary,
builder: (context, showAccessory, _) {
final List<Widget> children = [child];
if (showAccessory) {
children.add(
CellAccessoryContainer(accessories: accessories).positioned(
right: GridSize.cellContentInsets.right,
),
);
}
return Stack(
alignment: AlignmentDirectional.center,
fit: StackFit.expand,
children: children,
);
},
border: Border(
left: isPrimary ? borderSide : BorderSide.none,
right: borderSide,
bottom: borderSide,
),
);
}
}

View File

@ -6,7 +6,7 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_service.dar
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/desktop_field_cell.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart';